Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 91 additions & 58 deletions src/DeckardSchema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ export const DeckardSchema: React.FC<DeckardSchemaProps> = ({
results: 0,
});
const [focusedProperty, setFocusedProperty] = useState<string | null>(null);
const [scrollTarget, setScrollTarget] = useState<string | null>(null);
const [keyboardModalOpen, setKeyboardModalOpen] = useState(false);
const [examplesHidden, setExamplesHidden] = useState(false);

Expand Down Expand Up @@ -496,47 +497,77 @@ export const DeckardSchema: React.FC<DeckardSchemaProps> = ({
setPropertyStates(newStates);
}, [schema, autoExpand]);

// Handle URL hash navigation - only on mount
// Handle URL hash navigation - only on mount
// Handle URL hash navigation and hash changes
useEffect(() => {
const hash = typeof window !== 'undefined' ? window.location.hash : '';
if (hash) {
const fieldKey = hashToPropertyKey(hash);
const handleHashNavigation = () => {
const hash = typeof window !== 'undefined' ? window.location.hash : '';
if (hash) {
const fieldKey = hashToPropertyKey(hash);

// Update property states to expand path to target
setPropertyStates(prev => {
const newStates = { ...prev };
// Update property states to expand path to target
setPropertyStates(prev => {
const newStates = { ...prev };

// Expand all parent paths to make the target field visible
const pathParts = fieldKey.split('.');
// Expand all parent paths to make the target field visible
const pathParts = fieldKey.split('.');

for (let i = 1; i <= pathParts.length; i++) {
const parentPath = pathParts.slice(0, i).join('.');
for (let i = 1; i <= pathParts.length; i++) {
const parentPath = pathParts.slice(0, i).join('.');

if (newStates[parentPath]) {
newStates[parentPath] = {
...newStates[parentPath],
expanded: true,
};
} else {
newStates[parentPath] = {
expanded: true,
hasDetails: true,
matchesSearch: true,
isDirectMatch: false,
hasNestedMatches: false,
};
if (newStates[parentPath]) {
newStates[parentPath] = {
...newStates[parentPath],
expanded: true,
};
} else {
newStates[parentPath] = {
expanded: true,
hasDetails: true,
matchesSearch: true,
isDirectMatch: false,
hasNestedMatches: false,
};
}
}
}

return newStates;
});
return newStates;
});

// Set the focused property state for proper styling
setFocusedProperty(fieldKey);

// Set a flag to trigger scrolling after state updates
setScrollTarget(fieldKey);
}
};

// Handle initial hash on mount
handleHashNavigation();

// Set the focused property state for proper styling
setFocusedProperty(fieldKey);
// Listen for hash changes
if (typeof window !== 'undefined') {
window.addEventListener('hashchange', handleHashNavigation);
return () =>
window.removeEventListener('hashchange', handleHashNavigation);
}
}, []);

// Handle scrolling after state updates complete
useEffect(() => {
if (scrollTarget && typeof document !== 'undefined') {
const targetElement = document.getElementById(
propertyKeyToHash(scrollTarget)
);
if (targetElement && typeof targetElement.scrollIntoView === 'function') {
targetElement.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
setScrollTarget(null);
}
}, [scrollTarget, propertyStates]);

// Update search results
useEffect(() => {
setSearchState(prev => ({
Expand Down Expand Up @@ -744,37 +775,36 @@ export const DeckardSchema: React.FC<DeckardSchemaProps> = ({
// Navigation helpers
const focusProperty = useCallback((propertyKey: string) => {
setFocusedProperty(propertyKey);
setTimeout(() => {
if (typeof document === 'undefined' || typeof window === 'undefined') {
return;
}

const element = document.querySelector(
`[data-property-key="${propertyKey}"]`
if (typeof document === 'undefined' || typeof window === 'undefined') {
return;
}

const element = document.querySelector(
`[data-property-key="${propertyKey}"]`
) as HTMLElement;
if (element) {
// Focus the header container to trigger :focus-within like clicking does
const headerContainer = element.querySelector(
'.property-header-container'
) as HTMLElement;
if (element) {
// Focus the header container to trigger :focus-within like clicking does
const headerContainer = element.querySelector(
'.property-header-container'
) as HTMLElement;
if (headerContainer) {
headerContainer.focus();
} else {
element.setAttribute('tabindex', '-1');
element.focus();
}
if (headerContainer) {
headerContainer.focus();
} else {
element.setAttribute('tabindex', '-1');
element.focus();
}

const rect = element.getBoundingClientRect();
const viewportHeight =
typeof window !== 'undefined' ? window.innerHeight : 0;
const isInView = rect.top >= 0 && rect.bottom <= viewportHeight;
const rect = element.getBoundingClientRect();
const viewportHeight =
typeof window !== 'undefined' ? window.innerHeight : 0;
const isInView = rect.top >= 0 && rect.bottom <= viewportHeight;

// Only scroll if the element is not fully visible
if (!isInView && typeof element.scrollIntoView === 'function') {
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
// Only scroll if the element is not fully visible
if (!isInView && typeof element.scrollIntoView === 'function') {
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, 50);
}
}, []);

const getAllNavigableProperties = useCallback(() => {
Expand Down Expand Up @@ -1134,10 +1164,13 @@ export const DeckardSchema: React.FC<DeckardSchemaProps> = ({
const anchor = `#${propertyKeyToHash(propertyKey)}`;
const url = `${window.location.origin}${window.location.pathname}${anchor}`;

// Update the URL in the address bar (this will trigger native browser scrolling)
// Update the URL in the address bar
window.location.hash = anchor;

// Focus the current element
// Set scroll target for deterministic scrolling after state updates
setScrollTarget(propertyKey);

// Focus immediately since it doesn't depend on DOM updates
element.focus();

try {
Expand Down
Loading