-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
tmp: stacked context for page styles
A PoC of the stacked context paradigm where any edits are reset by the consuming component’s `onDestroy` lifecycle hook. The example sets the page background using both `+page`s and `+layout`s: 1. /(voters)/+layout: init the context with default bg-base-100 2. /(voters)/+page: base-300 for the front page 3. /questions/+layout: base-300 for the whole questions route 4. /questions/[categoryId]/+page base-100 for just the category intro pages 5. /(voters)/results/[entityType]/[entityId]/+page base-300 for the entity detail page NB. Ultimately, we would init the context in `/+layout` but we're making changes now only to the Voter App.
- Loading branch information
Showing
8 changed files
with
164 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import {setContext, getContext, hasContext} from 'svelte'; | ||
import {deepMerge} from '$lib/utils/merge'; | ||
import {stackedStore, type StackedStore} from '$lib/utils/stackedStore'; | ||
import {error} from '@sveltejs/kit'; | ||
|
||
const LAYOUT_CONTEXT_KEY = Symbol(); | ||
|
||
export const DEFAULT_PAGE_STYLES: PageStyles = { | ||
drawer: { | ||
background: 'bg-base-100' | ||
} | ||
} as const; | ||
|
||
/** | ||
* Initialize and return the context. This must be called before `getLayoutContext()` and cannot be called twice. | ||
* @returns The context object | ||
*/ | ||
export function initLayoutContext(): LayoutContext { | ||
if (hasContext(LAYOUT_CONTEXT_KEY)) error(500, 'InitLayoutContext() called for a second time'); | ||
|
||
const pageStyles = stackedStore<PageStyles, DeepPartial<PageStyles>>( | ||
DEFAULT_PAGE_STYLES, | ||
(current, value) => [ | ||
...current, | ||
deepMerge(structuredClone(current[current.length - 1]), structuredClone(value)) | ||
] | ||
); | ||
// We can add more reversion actions here when needed | ||
const revert = (index: number) => pageStyles.revert(index); | ||
|
||
return setContext<LayoutContext>(LAYOUT_CONTEXT_KEY, {pageStyles, revert}); | ||
} | ||
|
||
/** | ||
* Get the `LayoutContext` object. | ||
* @param onDestroy - The `onDestroy` callback for the component using the context. This is needed for automatic rolling back of any changes made to page styles or other context properties affecting layout. | ||
* @returns The `LayoutContext` object | ||
*/ | ||
export function getLayoutContext(onDestroy: (fn: () => unknown) => void) { | ||
if (!hasContext(LAYOUT_CONTEXT_KEY)) | ||
error(500, 'GetLayoutContext() called before initLayoutContext()'); | ||
const ctx = getContext<LayoutContext>(LAYOUT_CONTEXT_KEY); | ||
const currentIndex = ctx.pageStyles.getLength() - 1; | ||
onDestroy(() => ctx.revert(currentIndex)); | ||
return ctx; | ||
} | ||
|
||
export type LayoutContext = { | ||
/** | ||
* An store containing CSS classes used to customize different parts of the layout. | ||
*/ | ||
pageStyles: StackedStore<PageStyles, DeepPartial<PageStyles>>; | ||
/** | ||
* Called to revert any changes made to the `LayoutContext`. It will be automatically called when the component is destroyed. | ||
* @param index - The index in the stack that the changes should be rolled back to. | ||
*/ | ||
revert: (index: number) => void; | ||
}; | ||
|
||
type DeepPartial<T> = { | ||
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]; | ||
}; | ||
|
||
interface PageStyles { | ||
drawer: { | ||
background: 'bg-base-100' | 'bg-base-300'; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import {error} from '@sveltejs/kit'; | ||
import {writable, derived, get, type Writable, type Readable} from 'svelte/store'; | ||
|
||
/** | ||
* Create a simple stacked store which a appends items to an internally stored stack when set. The store can be reverted to any previous state with the `revert(index)` function. | ||
* @param initialValue - The first item of the stack. | ||
*/ | ||
export function simpleStackedStore<TItem>(initialValue: TItem): StackedStore<TItem> { | ||
return stackedStore(initialValue, (current, value) => [...current, value]); | ||
} | ||
|
||
/** | ||
* Create a stacked store which a appends items to an internally stored stack when set. The store can be reverted to any previous state with the `revert(index)` function. | ||
* @param initialValue - The first item of the stack. | ||
* @param updater - The function which is used to update the stack with new items. | ||
*/ | ||
export function stackedStore<TMerged, TAddition = TMerged>( | ||
initialValue: TMerged, | ||
updater: (current: Array<TMerged>, value: TAddition) => Array<TMerged> | ||
): StackedStore<TMerged, TAddition> { | ||
const stack = writable<Array<TMerged>>([initialValue]); | ||
const {subscribe} = derived(stack, ($stack) => $stack[$stack.length - 1]); | ||
|
||
const revert = (index: number): TMerged => { | ||
if (index < 0) error(500, 'StackedStore.revert: index cannot be negative'); | ||
const current = get(stack); | ||
if (index < current.length - 1) { | ||
current.splice(index + 1); | ||
stack.set(current); | ||
} | ||
return current[current.length - 1]; | ||
}; | ||
const set = (value: TAddition): void => stack.update((s) => updater(s, value)); | ||
const getLength = (): number => get(stack).length; | ||
|
||
return {getLength, revert, set, subscribe}; | ||
} | ||
|
||
/** | ||
* @typeParam TMerged - The type of the merged items in the stack, i.e., the type that is returned when subscribing to the stack. | ||
* @typeParam TAddition - The type of the items that can be added to the stack, which may differ from `TMerged`, e.g. by `Partial<TMerged>`. | ||
*/ | ||
export type StackedStore<TMerged, TAddition = TMerged> = { | ||
/** | ||
* @returns The current length of the stack. | ||
*/ | ||
getLength: () => number; | ||
/** | ||
* Revert the stack to the item at the given index. | ||
* @param index - The index of the item to revert to. If the index is out of bounds, the stack remains unchanged, but the last item is still returned. | ||
* @returns The last item in the stack after reverting. | ||
*/ | ||
revert: (index: number) => TMerged; | ||
/** | ||
* Add another item to the stack. | ||
*/ | ||
set: Writable<TAddition>['set']; | ||
/** | ||
* Subscribe to the last item in the stack. | ||
*/ | ||
subscribe: Readable<TMerged>['subscribe']; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.