Make application shell components package-native
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nodedc/ui-dom",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"type": "module",
|
||||
"files": ["dist"],
|
||||
"main": "./dist/index.js",
|
||||
@@ -16,6 +16,6 @@
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nodedc/ui-core": "0.3.0"
|
||||
"@nodedc/ui-core": "0.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
export interface ApplicationWorkspaceSnapshot<ViewId extends string = string> {
|
||||
navigationOpen: boolean;
|
||||
activeView: ViewId | null;
|
||||
contentOpen: boolean;
|
||||
contentExpanded: boolean;
|
||||
}
|
||||
|
||||
export interface ApplicationWorkspaceController<ViewId extends string = string> {
|
||||
getState: () => ApplicationWorkspaceSnapshot<ViewId>;
|
||||
openNavigation: () => void;
|
||||
toggleNavigation: () => void;
|
||||
closeNavigation: () => void;
|
||||
openView: (view: ViewId) => void;
|
||||
closeView: () => void;
|
||||
setContentExpanded: (expanded: boolean) => void;
|
||||
}
|
||||
|
||||
export interface ApplicationWorkspaceOptions<ViewId extends string = string> {
|
||||
root?: HTMLElement;
|
||||
navigationOpen?: boolean;
|
||||
activeView?: ViewId | null;
|
||||
contentExpanded?: boolean;
|
||||
onChange?: (state: ApplicationWorkspaceSnapshot<ViewId>) => void;
|
||||
}
|
||||
|
||||
export function createApplicationWorkspaceController<ViewId extends string = string>({
|
||||
root,
|
||||
navigationOpen = false,
|
||||
activeView = null,
|
||||
contentExpanded = true,
|
||||
onChange,
|
||||
}: ApplicationWorkspaceOptions<ViewId> = {}): ApplicationWorkspaceController<ViewId> {
|
||||
let state: ApplicationWorkspaceSnapshot<ViewId> = {
|
||||
navigationOpen: navigationOpen || activeView !== null,
|
||||
activeView,
|
||||
contentOpen: activeView !== null,
|
||||
contentExpanded,
|
||||
};
|
||||
|
||||
const commit = (patch: Partial<ApplicationWorkspaceSnapshot<ViewId>>) => {
|
||||
state = { ...state, ...patch };
|
||||
state.contentOpen = state.activeView !== null;
|
||||
root?.toggleAttribute("data-navigation-open", state.navigationOpen);
|
||||
root?.toggleAttribute("data-content-open", state.contentOpen);
|
||||
root?.toggleAttribute("data-content-expanded", state.contentOpen && state.contentExpanded);
|
||||
onChange?.({ ...state });
|
||||
};
|
||||
|
||||
commit({});
|
||||
|
||||
return {
|
||||
getState: () => ({ ...state }),
|
||||
openNavigation: () => commit({ navigationOpen: true }),
|
||||
toggleNavigation: () => commit({ navigationOpen: !state.navigationOpen, activeView: state.navigationOpen ? null : state.activeView }),
|
||||
closeNavigation: () => commit({ navigationOpen: false, activeView: null }),
|
||||
openView: (view) => commit({ navigationOpen: true, activeView: view, contentExpanded: true }),
|
||||
closeView: () => commit({ activeView: null }),
|
||||
setContentExpanded: (expanded) => commit({ contentExpanded: expanded }),
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
export { applyNodedcTheme, clearNodedcAccent, createAccentVariables, readableTextRgb } from "@nodedc/ui-core";
|
||||
export * from "./floating";
|
||||
export * from "./modal";
|
||||
export * from "./select";
|
||||
|
||||
export * from "./applicationWorkspace.js";
|
||||
export * from "./floating.js";
|
||||
export * from "./mediaSource.js";
|
||||
export * from "./modal.js";
|
||||
export * from "./select.js";
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
export type MediaSource = "file" | "url";
|
||||
|
||||
export interface MediaSourceController {
|
||||
getSource: () => MediaSource;
|
||||
setSource: (source: MediaSource) => void;
|
||||
destroy: () => void;
|
||||
}
|
||||
|
||||
export interface MediaSourceControllerOptions {
|
||||
root: HTMLElement;
|
||||
source?: MediaSource;
|
||||
onChange?: (source: MediaSource) => void;
|
||||
}
|
||||
|
||||
export function createMediaSourceController({ root, source = "file", onChange }: MediaSourceControllerOptions): MediaSourceController {
|
||||
const options = Array.from(root.querySelectorAll<HTMLElement>("[data-nodedc-media-source-option]"));
|
||||
const panels = Array.from(root.querySelectorAll<HTMLElement>("[data-nodedc-media-source-panel]"));
|
||||
let current = source;
|
||||
|
||||
const render = () => {
|
||||
root.dataset.source = current;
|
||||
options.forEach((option) => {
|
||||
const active = option.dataset.nodedcMediaSourceOption === current;
|
||||
option.dataset.active = active ? "true" : "false";
|
||||
option.setAttribute("aria-pressed", String(active));
|
||||
});
|
||||
panels.forEach((panel) => {
|
||||
panel.hidden = panel.dataset.nodedcMediaSourcePanel !== current;
|
||||
});
|
||||
};
|
||||
|
||||
const setSource = (next: MediaSource) => {
|
||||
if (next === current) return;
|
||||
current = next;
|
||||
render();
|
||||
onChange?.(current);
|
||||
};
|
||||
|
||||
const handlers = options.map((option) => {
|
||||
const handler = () => {
|
||||
const next = option.dataset.nodedcMediaSourceOption;
|
||||
if (next === "file" || next === "url") setSource(next);
|
||||
};
|
||||
option.addEventListener("click", handler);
|
||||
return { option, handler };
|
||||
});
|
||||
|
||||
render();
|
||||
|
||||
return {
|
||||
getSource: () => current,
|
||||
setSource,
|
||||
destroy: () => handlers.forEach(({ option, handler }) => option.removeEventListener("click", handler)),
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createFloatingLayer, type FloatingLayerController, type FloatingLayerOptions } from "./floating";
|
||||
import { createFloatingLayer, type FloatingLayerController, type FloatingLayerOptions } from "./floating.js";
|
||||
|
||||
export interface SelectControllerOptions<T extends string> extends Omit<FloatingLayerOptions, "closeOnSelect"> {
|
||||
value: T;
|
||||
|
||||
Reference in New Issue
Block a user