reconsile cdrl error

This commit is contained in:
2026-07-15 13:01:18 -04:00
parent 4e378842e4
commit dacd68b617

View File

@@ -1,4 +1,4 @@
import { createSignal, onCleanup, onMount, Show, For, JSXElement } from "solid-js";
import { createSignal, createMemo, onCleanup, onMount, Show, For, JSXElement } from "solid-js";
// CrmTabGroup / CrmSubTabGroup — drop-in, behaviourally identical siblings of
// TabGroup (same props, storageKey syncing, controlled/uncontrolled index) with
@@ -26,13 +26,15 @@ function resolveCrmBadge(badge: number | undefined): number | undefined {
// Shared active-index state: mirrors TabGroup exactly (localStorage persistence,
// cross-component sync via synthetic storage events, optional controlled index).
function createCrmTabState(props: CrmTabGroupProps) {
// Takes the memoized `items` accessor (not props.items) so length checks read the
// same single evaluation the render does — see the createMemo note in CrmTabGroup.
function createCrmTabState(props: CrmTabGroupProps, items: () => CrmTabItem[]) {
const getInitialIndex = () => {
if (props.storageKey) {
const stored = localStorage.getItem(props.storageKey);
if (stored !== null) {
const parsed = parseInt(stored, 10);
if (!isNaN(parsed) && parsed >= 0 && parsed < props.items.length) {
if (!isNaN(parsed) && parsed >= 0 && parsed < items().length) {
return parsed;
}
}
@@ -65,7 +67,7 @@ function createCrmTabState(props: CrmTabGroupProps) {
const handler = (e: StorageEvent) => {
if (e.key !== props.storageKey || e.newValue == null) return;
const n = parseInt(e.newValue, 10);
if (!isNaN(n) && n >= 0 && n < props.items.length && n !== _activeIndex()) {
if (!isNaN(n) && n >= 0 && n < items().length && n !== _activeIndex()) {
_setActiveIndex(n);
props.onTabChange && props.onTabChange(n);
}
@@ -103,11 +105,19 @@ const CRM_TAB_ACTIVE = "border-x border-t-2 border-t-sky-700 text-primary";
const CRM_TAB_BADGE = "inline-flex items-center justify-center min-w-5 h-5 px-1 text-xs font-semibold bg-primary text-white rounded-full";
export function CrmTabGroup(props: CrmTabGroupProps) {
const { activeIndex, setActiveIndex } = createCrmTabState(props);
// Memoize props.items: callers pass items={[{content: <Comp/>}]} where each
// `content` is eager JSX, and Solid re-evaluates a `prop={expr}` getter on every
// read. Reading props.items directly (in the button For, the content For, and the
// length checks reached from the onclick handler) would re-run createComponent on
// every tab's content each time — and the read inside setActiveIndex happens outside
// the render owner, so those rebuilt components lose their provider context
// (useToast/useModal throw). The memo evaluates the tree once, inside this owner.
const items = createMemo(() => props.items);
const { activeIndex, setActiveIndex } = createCrmTabState(props, items);
return <div class="w-full">
<div class={CRM_TAB_ROW}>
<For each={props.items}>{(item: CrmTabItem, index: () => number) => (
<For each={items()}>{(item: CrmTabItem, index: () => number) => (
<button type="button" onclick={() => setActiveIndex(index())} class={CRM_TAB_BASE + " " + (index() === activeIndex() ? CRM_TAB_ACTIVE : CRM_TAB_INACTIVE)}>
{item.title}
<Show when={(() => {
@@ -121,7 +131,7 @@ export function CrmTabGroup(props: CrmTabGroupProps) {
<div class="flex-1 border-b border-line-strong"></div>
</div>
<div>
{crmTabContent(props.items, activeIndex)}
{crmTabContent(items(), activeIndex)}
</div>
</div>;
}
@@ -141,12 +151,14 @@ const CRM_SUBTAB_INACTIVE = "bg-surface text-ink-soft hover:bg-surface-raised ho
const CRM_SUBTAB_BADGE = "inline-flex items-center justify-center min-w-5 h-5 px-1 text-xs font-semibold bg-black/10 text-current rounded-full";
export function CrmSubTabGroup(props: CrmTabGroupProps) {
const { activeIndex, setActiveIndex } = createCrmTabState(props);
// See CrmTabGroup: memoize eager `content` JSX so it's built once, in-owner.
const items = createMemo(() => props.items);
const { activeIndex, setActiveIndex } = createCrmTabState(props, items);
return <div class="w-full pt-3">
<div class={CRM_SUBTAB_WRAP}>
<div class={CRM_SUBTAB_GROUP}>
<For each={props.items}>{(item: CrmTabItem, index: () => number) => (
<For each={items()}>{(item: CrmTabItem, index: () => number) => (
<button type="button" onclick={() => setActiveIndex(index())} class={CRM_SUBTAB_BASE + (index() > 0 ? " " + CRM_SUBTAB_DIVIDER : "") + " " + (index() === activeIndex() ? CRM_SUBTAB_ACTIVE : CRM_SUBTAB_INACTIVE)}>
{item.title}
<Show when={(() => {
@@ -160,7 +172,7 @@ export function CrmSubTabGroup(props: CrmTabGroupProps) {
</div>
</div>
<div class="pt-3">
{crmTabContent(props.items, activeIndex)}
{crmTabContent(items(), activeIndex)}
</div>
</div>;
}