Make application shell components package-native

This commit is contained in:
DCCONSTRUCTIONS
2026-07-10 12:41:12 +03:00
parent 9261d3af36
commit ebae662b52
52 changed files with 1036 additions and 185 deletions
@@ -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 }),
};
}
+5 -4
View File
@@ -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";
+55
View File
@@ -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 -1
View File
@@ -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;