
Security News
npm Adopts OIDC for Trusted Publishing in CI/CD Workflows
npm now supports Trusted Publishing with OIDC, enabling secure package publishing directly from CI/CD workflows without relying on long-lived tokens.
kui-framework
Advanced tools
A lightweight and flexible UI framework for building modern web applications.
You can create all kinds of interfaces quickly with this framework.
The code in src/lib
is implementing a lightweight UI framework for building web applications. It provides a set of reusable components and utilities to create interactive user interfaces. Here's a brief overview of its functionality:
Simple Example:
import { Container, Button, Form, Animation } from "../index";
export function loginexample()
{
const app = new Container();
const login = new Button({
text: "Login",
hoverAnimation: Animation("grow", 300),
});
login.setOnClick(() => alert("Hello!"));
const loginForm = new Form({
fields: [
{ type: "text", name: "username", label: "Username" },
{ type: "password", name: "password", label: "Password" },
],
onSubmit: (data) => console.log("Login:", data),
});
app.addChild(loginForm);
app.addChild(login);
app.mount(document.body);
}
This example creates a simple app with a greeting button and a login form.
The framework is built around the concept of Views, which are the basic building blocks of the UI. The View class provides core functionality for creating and managing UI elements.
// view.ts
import { AnimationOptions } from "./animations";
export interface ViewOptions {
id?: string;
className?: string;
template?: string;
}
export class View {
protected element: HTMLElement;
protected children: View[] = [];
private mountedPromise: Promise<void>;
private mountedResolve!: () => void;
constructor(options: ViewOptions = {}) {
this.element = document.createElement("div");
if (options.id) this.element.id = options.id;
if (options.className) this.element.className = options.className;
if (options.template) this.setTemplate(options.template);
this.mountedPromise = new Promise((resolve) => {
this.mountedResolve = resolve;
});
}
setTemplate(template: string): void {
this.element.innerHTML = template;
}
appendChild(child: View): void {
this.children.push(child);
this.element.appendChild(child.getElement());
}
getElement(): HTMLElement {
return this.element;
}
async onMount(): Promise<void> {
// To be overridden by subclasses
}
async onUnmount(): Promise<void> {
// To be overridden by subclasses
}
mounted(): Promise<void> {
return this.mountedPromise;
}
mount(parent: HTMLElement): void {
parent.appendChild(this.element);
this.mountedResolve();
this.onMount();
}
unmount(): void {
this.element.remove();
this.onUnmount();
}
centerInRow(): this {
this.element.style.justifySelf = "center";
return this;
}
centerInColumn(): this {
this.element.style.alignSelf = "center";
return this;
}
}
The Container class extends View and allows for more complex layouts, including grid systems and flexible positioning of child elements.
// container.ts
import { View, ViewOptions } from "./view";
import { Button, ButtonOptions } from "./button";
import { Form, FormOptions } from "./form";
import { framework } from "./framework";
// Initialize the framework
framework.init();
export interface GridOptions {
columns: number | string;
rows?: number | string;
gap?: string;
}
export type RowAlignment =
| "left"
| "center"
| "right"
| "space-between"
| "space-around"
| "space-evenly";
export interface ContainerOptions extends ViewOptions {
gap?: string;
padding?: string;
grid?: GridOptions;
scrollable?: boolean;
rowAlignment?: RowAlignment;
equalWidthChildren?: boolean;
columns?: number | string;
rows?: number | string;
}
Other components like Button, Form, and List build upon these base classes to provide specific functionality. For example, the Button class adds click handling and hover effects.
// button.ts
import { View, ViewOptions } from "./view";
import { renderTemplate } from "./templating";
import { AnimationOptions, applyAnimation } from "./animations";
export interface ButtonOptions extends ViewOptions {
text: string;
onClick?: () => void;
color?: string;
hoverColor?: string;
hoverAnimation?: AnimationOptions;
clickAnimation?: AnimationOptions;
}
const buttonTemplate = `
<button class="ui-button" style="background-color: {{color}}">
{{text}}
</button>
`;
export class Button extends View {
private buttonElement: HTMLButtonElement;
constructor(options: ButtonOptions) {
super({
...options,
template: renderTemplate(buttonTemplate, {
text: options.text,
color: options.color || "#007bff",
}),
});
this.buttonElement = this.element.querySelector(
"button"
) as HTMLButtonElement;
if (options.onClick) {
this.buttonElement.addEventListener("click", () => {
if (options.clickAnimation) {
applyAnimation(this.buttonElement, options.clickAnimation);
}
// Add null check before invoking onClick
options.onClick?.();
});
}
if (options.hoverAnimation || options.hoverColor) {
this.buttonElement.addEventListener("mouseenter", () => {
if (options.hoverAnimation) {
applyAnimation(this.buttonElement, options.hoverAnimation);
}
if (options.hoverColor) {
this.buttonElement.style.backgroundColor = options.hoverColor;
}
});
this.buttonElement.addEventListener("mouseleave", () => {
if (options.hoverAnimation) {
this.buttonElement.style.animation = "none";
}
if (options.hoverColor) {
this.buttonElement.style.backgroundColor = options.color || "#007bff";
}
});
}
}
setText(text: string): void {
this.buttonElement.textContent = text;
}
setColor(color: string): void {
this.buttonElement.style.backgroundColor = color;
}
}
The framework also includes a templating system for rendering dynamic content, and an animation system (referenced in button.ts) for adding visual effects to UI elements.
// templating.ts
export function renderTemplate(
template: string,
data: { [key: string]: any }
): string {
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => data[key] || "");
}
Complex Example:
// emailClientExample.ts
import { Container, View, Button, Form, List } from "../index";
import { initStyles, appendStyles } from "../styles";
interface Email {
id: number;
from: string;
subject: string;
body: string;
read: boolean;
}
class EmailItem extends View {
constructor(email: Email, onSelect: (id: number) => void) {
super({
className: `email-item ${email.read ? "read" : "unread"}`,
template: `
<div class="email-sender">${email.from}</div>
<div class="email-subject">${email.subject}</div>
`,
});
this.element.addEventListener("click", () => onSelect(email.id));
}
}
class EmailView extends View {
constructor(email: Email | null) {
super({
className: "email-view",
template: email
? `
<h2>${email.subject}</h2>
<p><strong>From:</strong> ${email.from}</p>
<div class="email-body">${email.body}</div>
`
: "<p>Select an email to view</p>",
});
}
}
export function createEmailClientExample(): View {
initStyles();
appendStyles(emailClientCSS);
const mainContainer = new Container({
className: "email-client",
gap: "20px",
});
// Header
const header = new View({
className: "header",
template: "<h1>Simple Email Client</h1>",
});
mainContainer.addChild(header);
// Main content area
const content = new Container({
className: "content",
gap: "20px",
columns: "150px 300px 1fr", // Define three columns
});
mainContainer.addChild(content);
// Sidebar (Folders)
const sidebar = new Container({
className: "sidebar",
gap: "10px",
equalWidthChildren: true,
});
["Inbox", "Sent", "Drafts", "Trash"].forEach((folder) => {
sidebar.addChild(
new Button({
text: folder,
hoverColor: "#0056b3",
hoverAnimation: { type: "grow", duration: 300 },
clickAnimation: { type: "push", duration: 200 },
onClick: () => console.log(`Switched to ${folder}`),
})
);
});
content.addChild(sidebar);
// Email list
const emailListContainer = new Container({
className: "email-list-container",
gap: "10px",
});
content.addChild(emailListContainer);
const emailList = new List({ className: "email-list" });
emailListContainer.addChild(emailList);
// Compose button
const composeButton = new Button({
text: "Compose",
onClick: () => showComposeForm(),
});
emailListContainer.addChild(composeButton);
// Email view
const emailViewContainer = new Container({
className: "email-view-container",
gap: "10px",
});
content.addChild(emailViewContainer);
let currentEmailView = new EmailView(null);
emailViewContainer.addChild(currentEmailView);
// Sample emails
const emails: Email[] = [
{
id: 1,
from: "john@example.com",
subject: "Hello",
body: "Hi there!",
read: false,
},
{
id: 2,
from: "jane@example.com",
subject: "Meeting tomorrow",
body: "Don't forget our meeting.",
read: true,
},
{
id: 3,
from: "boss@example.com",
subject: "Urgent: Report needed",
body: "Please send the report ASAP.",
read: false,
},
];
function renderEmails() {
emailList.clear();
emails.forEach((email) => {
emailList.addItem(new EmailItem(email, selectEmail));
});
}
function selectEmail(id: number) {
const email = emails.find((e) => e.id === id);
if (email) {
email.read = true;
currentEmailView = new EmailView(email);
emailViewContainer.clear();
emailViewContainer.addChild(currentEmailView);
renderEmails(); // Re-render to update read/unread status
}
}
function showComposeForm() {
const composeForm = new Form({
fields: [
{ type: "text", name: "to", label: "To:", required: true },
{ type: "text", name: "subject", label: "Subject:", required: true },
{ type: "textarea", name: "body", label: "Message:", required: true },
],
onSubmit: (data) => {
console.log("Sending email:", data);
// Here you would typically send the email and update the sent folder
emailViewContainer.clear();
emailViewContainer.addChild(new EmailView(null));
},
});
emailViewContainer.clear();
emailViewContainer.addChild(composeForm);
}
renderEmails();
return mainContainer;
}
const emailClientCSS = `
.email-client {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.content {
display: grid;
grid-template-columns: 150px 300px 1fr;
gap: 20px;
}
.sidebar .ui-button button {
width: 100%;
text-align: left;
padding: 10px;
}
.email-list-container {
overflow-y: auto;
max-height: 80vh;
}
.email-item {
padding: 10px;
border-bottom: 1px solid #40444B;
cursor: pointer;
}
.email-item:hover {
background-color: #40444B;
}
.email-item.unread {
font-weight: bold;
}
.email-sender {
font-size: 0.9em;
}
.email-subject {
font-size: 1em;
}
.email-view {
background-color: #40444B;
padding: 20px;
border-radius: 4px;
}
.email-body {
margin-top: 20px;
white-space: pre-wrap;
}
`;
This example creates a more complex task management application using various components and demonstrating their interactions.
The framework includes a plugin system that allows for extending its functionality.
// plugin-system.ts
/**
* Interface for plugins that can be registered with the PluginManager.
*/
interface Plugin {
readonly name: string;
install(framework: any): void;
}
To add a plugin:
Plugin
interface:const myPlugin: Plugin = {
name: 'myPlugin',
install(framework: any) {
// Add new functionality to the framework
framework.newMethod = () => console.log('New method added by plugin');
}
};
import { framework } from './lib/framework';
framework.registerPlugin(myPlugin);
framework.newMethod(); // Outputs: "New method added by plugin"
This plugin system allows for modular extension of the framework's capabilities without modifying its core code. Here is a state management plugin:
import { Plugin } from '../plugin-system';
type Reducer<S, A> = (state: S, action: A) => S;
type Listener = () => void;
type Unsubscribe = () => void;
interface Store<S, A> {
getState: () => S;
dispatch: (action: A) => void;
subscribe: (listener: Listener) => Unsubscribe;
}
const stateManagementPlugin: Plugin = {
name: 'StateManagementPlugin',
install(framework) {
framework.createStore = function<S, A>(reducer: Reducer<S, A>, initialState: S): Store<S, A> {
let state = initialState;
const listeners: Listener[] = [];
const store: Store<S, A> = {
getState: () => state,
dispatch: (action: A) => {
state = reducer(state, action);
listeners.forEach(listener => listener());
},
subscribe: (listener: Listener): Unsubscribe => {
listeners.push(listener);
return () => {
const index = listeners.indexOf(listener);
if (index > -1) listeners.splice(index, 1);
};
}
};
return store;
};
framework.View.prototype.connectToStore = function<S, P>(store: Store<S, any>, mapStateToProps: (state: S) => P) {
const updateView = () => {
const stateProps = mapStateToProps(store.getState());
Object.assign(this, stateProps);
this.render();
};
const unsubscribe = store.subscribe(updateView);
updateView();
// Clean up subscription when view is unmounted
const originalUnmount = this.unmount;
this.unmount = () => {
unsubscribe();
originalUnmount.call(this);
};
};
}
};
export default stateManagementPlugin;
This plugin adds a Redux-like state management system to the framework:
FAQs
A lightweight framework for building web applications.
The npm package kui-framework receives a total of 0 weekly downloads. As such, kui-framework popularity was classified as not popular.
We found that kui-framework demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
npm now supports Trusted Publishing with OIDC, enabling secure package publishing directly from CI/CD workflows without relying on long-lived tokens.
Research
/Security News
A RubyGems malware campaign used 60 malicious packages posing as automation tools to steal credentials from social media and marketing tool users.
Security News
The CNA Scorecard ranks CVE issuers by data completeness, revealing major gaps in patch info and software identifiers across thousands of vulnerabilities.