{"_id":"@ardeshirvalipoor/baseline","_rev":"2-62c7c0794784cf6c5a6abad9e7fa20d3","name":"@ardeshirvalipoor/baseline","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@ardeshirvalipoor/baseline","version":"1.0.0","keywords":["ui","framework","spa","router","imperative","minimal","typescript"],"author":{"name":"ardeshirvalipoor"},"license":"MIT","_id":"@ardeshirvalipoor/baseline@1.0.0","maintainers":[{"name":"ardeshirvalipoor","email":"ardeshir.valipoor@gmail.com"}],"homepage":"https://github.com/ardeshirvalipoor/base-ui#readme","bugs":{"url":"https://github.com/ardeshirvalipoor/base-ui/issues"},"dist":{"shasum":"5bcdbbc22e0aebf66960c7fa489e6182254980f7","tarball":"https://registry.npmjs.org/@ardeshirvalipoor/baseline/-/baseline-1.0.0.tgz","fileCount":35,"integrity":"sha512-TTqVZQyVrFdu9LdiKPD2+pqmpdluRJ7+OjLXmSFfUFiORhsl4YUmuYqnC583s/K0+i0l8QMo+CNaBSE1ER5bMA==","signatures":[{"sig":"MEUCIQD0PT8kz8mhr9LdEwOi2x7DsBifNE+ml4d7JNYluV5QkAIgfWq5NXt8mhggL5AOxADadjUVjOmqHupoLBbUB4sb8tY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":56781},"main":"index.ts","types":"index.ts","_npmUser":{"name":"ardeshirvalipoor","email":"ardeshir.valipoor@gmail.com"},"repository":{"url":"git+https://github.com/ardeshirvalipoor/base-ui.git","type":"git"},"_npmVersion":"11.6.0","description":"A minimal, imperative UI framework for building fast web apps. No virtual DOM, no magic, no dependencies.","directories":{},"_nodeVersion":"22.17.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{},"peerDependencies":{},"_npmOperationalInternal":{"tmp":"tmp/baseline_1.0.0_1766683624114_0.1514927278625695","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@ardeshirvalipoor/baseline","version":"1.0.1","description":"A minimal, imperative UI framework for building fast web apps. No virtual DOM, no magic, no dependencies.","main":"index.ts","types":"index.ts","keywords":["ui","framework","spa","router","imperative","minimal","typescript"],"author":{"name":"ardeshirvalipoor"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/ardeshirvalipoor/base-ui.git"},"peerDependencies":{},"publishConfig":{"access":"public"},"devDependencies":{},"_id":"@ardeshirvalipoor/baseline@1.0.1","bugs":{"url":"https://github.com/ardeshirvalipoor/base-ui/issues"},"homepage":"https://github.com/ardeshirvalipoor/base-ui#readme","_nodeVersion":"22.17.1","_npmVersion":"11.6.0","dist":{"integrity":"sha512-ctRfXzfUAjSSSc6OdonvUyBvC+p4MBNLnawaec4Q5PpuKHbTljhCeREs6rRHcocqyno56K4upsGdclxIB0mAJw==","shasum":"25e842ee2592ce46950c59f12c932213d7a977a2","tarball":"https://registry.npmjs.org/@ardeshirvalipoor/baseline/-/baseline-1.0.1.tgz","fileCount":35,"unpackedSize":56781,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIE06h8pI2JWubB+aK4oE+18dxj6nctPm1wJIOvOYzBLMAiEApMDb3TlsZvW9ICqsdCcMIu2lQVlO9PyqtubUBRY00L4="}]},"_npmUser":{"name":"ardeshirvalipoor","email":"ardeshir.valipoor@gmail.com"},"directories":{},"maintainers":[{"name":"ardeshirvalipoor","email":"ardeshir.valipoor@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/baseline_1.0.1_1766684095825_0.6455769537964113"},"_hasShrinkwrap":false}},"time":{"created":"2025-12-25T17:27:04.044Z","modified":"2025-12-25T17:34:56.219Z","1.0.0":"2025-12-25T17:27:04.251Z","1.0.1":"2025-12-25T17:34:56.016Z"},"bugs":{"url":"https://github.com/ardeshirvalipoor/base-ui/issues"},"author":{"name":"ardeshirvalipoor"},"license":"MIT","homepage":"https://github.com/ardeshirvalipoor/base-ui#readme","keywords":["ui","framework","spa","router","imperative","minimal","typescript"],"repository":{"type":"git","url":"git+https://github.com/ardeshirvalipoor/base-ui.git"},"description":"A minimal, imperative UI framework for building fast web apps. No virtual DOM, no magic, no dependencies.","maintainers":[{"name":"ardeshirvalipoor","email":"ardeshir.valipoor@gmail.com"}],"readme":"# Base\r\n\r\nA minimal, imperative UI framework for building fast web apps. No virtual DOM, no magic, no dependencies.\r\n\r\n**~1000 lines** of TypeScript that gives you everything you need for SPAs.\r\n\r\n## Installation\r\n\r\n```bash\r\nnpm install baseline\r\n```\r\n\r\n## Why Base?\r\n\r\n- **Predictable** - What you write is what happens. No hidden lifecycle, no reactivity magic.\r\n- **Fast** - Direct DOM manipulation. No diffing algorithms.\r\n- **Tiny** - The entire framework is smaller than most framework's \"hello world\" bundle.\r\n- **Stable** - No dependencies means no breaking changes from upstream.\r\n\r\n## Quick Start\r\n\r\n```typescript\r\nimport { Div, Button, router } from '@nichobase/base'\r\n\r\nconst App = () => {\r\n    const app = Div()\r\n    const count = { value: 0 }\r\n    \r\n    const counter = Div('0')\r\n    counter.style({ fontSize: '48px', textAlign: 'center' })\r\n    \r\n    const btn = Button('Click me')\r\n    btn.on('click', () => {\r\n        count.value++\r\n        counter.text(String(count.value))\r\n    })\r\n    \r\n    app.style({ display: 'flex', flexDirection: 'column', gap: '20px', padding: '40px' })\r\n    app.append(counter, btn)\r\n    \r\n    return app\r\n}\r\n\r\ndocument.body.appendChild(App().el)\r\n```\r\n\r\n## Core Concepts\r\n\r\n### Components\r\n\r\nEvery component is created with the `Base()` factory:\r\n\r\n```typescript\r\nimport { Base } from 'base/components/base'\r\n\r\nconst card = Base('div')   // Creates a <div>\r\ncard.el                     // The actual DOM element\r\ncard.id                     // Unique identifier\r\n```\r\n\r\nOr use pre-made native components:\r\n\r\n```typescript\r\nimport { Div, Button, Input, Span, A, Img } from 'base/components/native'\r\n\r\nconst container = Div('Hello')\r\nconst btn = Button('Click')\r\nconst input = Input('Enter name...', 'text')\r\n```\r\n\r\n### Events\r\n\r\nComponents have a built-in event emitter:\r\n\r\n```typescript\r\nconst btn = Button()\r\n\r\n// Listen to events\r\nbtn.on('click', () => console.log('clicked!'))\r\nbtn.on('mounted', () => console.log('in the DOM'))\r\n\r\n// Listen once\r\nbtn.once('click', () => console.log('first click only'))\r\n\r\n// Emit custom events\r\nbtn.emit('my-event', { data: 123 })\r\n\r\n// Remove listener\r\nbtn.off('click', handler)\r\n```\r\n\r\n### Styling\r\n\r\nTwo approaches: inline styles or CSS Modules.\r\n\r\n#### Inline Styles\r\n\r\nUse for dynamic values and simple components:\r\n\r\n```typescript\r\n// Inline styles\r\ncard.style({ \r\n    opacity: '0.5',\r\n    transform: 'translateY(-10px)',\r\n    transition: 'all 0.3s ease',\r\n})\r\n\r\n// Chained styles\r\ncard.style({ opacity: '0' })\r\n    .style({ opacity: '1' })\r\n```\r\n\r\n#### CSS Modules (Recommended)\r\n\r\nUse `.module.css` files for scoped, maintainable styles:\r\n\r\n**card/index.module.css**\r\n```css\r\n.base {\r\n    background: #fff;\r\n    border-radius: 12px;\r\n    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\r\n    padding: 16px;\r\n    transition: all 0.3s ease;\r\n}\r\n\r\n.base:hover {\r\n    box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);\r\n    transform: translateY(-4px);\r\n}\r\n\r\n.dark {\r\n    background: #1a1a1a;\r\n    color: #fff;\r\n}\r\n```\r\n\r\n**card/index.ts**\r\n```typescript\r\nimport { Div } from '../../base/components/native/div'\r\nimport styles from './index.module.css'\r\n\r\nexport const Card = () => {\r\n    const base = Div()\r\n    base.addClass(styles.base)\r\n    \r\n    // Toggle classes\r\n    base.addClass(styles.dark)     // Add dark mode\r\n    base.removeClass(styles.dark)  // Remove dark mode\r\n    base.toggleClass(styles.dark, isDark)  // Conditional\r\n    \r\n    return base\r\n}\r\n```\r\n\r\n#### Project Structure with CSS Modules\r\n\r\n```\r\ncomponents/\r\n├── card/\r\n│   ├── index.ts           # Component logic\r\n│   └── index.module.css   # Scoped styles\r\n├── button/\r\n│   ├── index.ts\r\n│   └── index.module.css\r\npages/\r\n├── home/\r\n│   ├── index.ts\r\n│   └── index.module.css\r\n```\r\n\r\n#### Rollup Configuration\r\n\r\nEnsure your `rollup.config.js` includes postcss:\r\n\r\n```javascript\r\nimport postcss from 'rollup-plugin-postcss'\r\n\r\nexport default {\r\n    plugins: [\r\n        postcss({\r\n            modules: true,        // Enable CSS modules\r\n            extract: 'bundle.css', // Output file\r\n            minimize: true,\r\n        }),\r\n    ],\r\n}\r\n```\r\n\r\nLink the CSS in your HTML:\r\n```html\r\n<link rel=\"stylesheet\" href=\"/bundle.css\">\r\n```\r\n\r\n### DOM Tree\r\n\r\n```typescript\r\nconst list = Div()\r\nconst item1 = Div('First')\r\nconst item2 = Div('Second')\r\n\r\n// Append children\r\nlist.append(item1, item2)\r\n\r\n// Prepend\r\nlist.prepend(Div('Zero'))\r\n\r\n// Conditional rendering\r\nlist.append(\r\n    isLoggedIn && UserProfile(),\r\n    showFooter ? Footer() : null\r\n)\r\n\r\n// Remove\r\nitem1.remove()\r\n\r\n// Empty all children\r\nlist.empty()\r\n\r\n// Access children\r\nlist.getChildren()\r\n```\r\n\r\n### Lifecycle\r\n\r\n```typescript\r\nconst card = Div()\r\n\r\ncard.on('mounted', () => {\r\n    // Called when component enters the DOM\r\n    console.log('Card is visible')\r\n})\r\n\r\ncard.on('unmounted', () => {\r\n    // Called when component is removed\r\n    console.log('Card removed')\r\n})\r\n```\r\n\r\n## Router\r\n\r\nBase includes a full-featured SPA router with page lifecycle management. Pages stay in DOM and visibility toggles for smooth transitions.\r\n\r\n### Basic Setup\r\n\r\n```typescript\r\nimport { Div } from 'base/components/native/div'\r\nimport router from 'base/lib/router'\r\nimport { HomePage } from './pages/home'\r\nimport { AboutPage } from './pages/about'\r\nimport { UserPage } from './pages/user'\r\n\r\nconst view = Div()  // Container for pages\r\nconst app = Div()\r\napp.append(view)\r\n\r\n// Configure routes - pages stay in DOM, visibility toggles\r\nrouter.routes({\r\n    '/': HomePage,\r\n    '/about': AboutPage,\r\n    '/users/:id': UserPage,\r\n}, view)\r\n\r\ndocument.body.appendChild(app.el)\r\n```\r\n\r\n### Route Configuration\r\n\r\n```typescript\r\nrouter.routes({\r\n    // Simple routes\r\n    '/': HomePage,\r\n    '/about': AboutPage,\r\n    \r\n    // Dynamic parameters\r\n    '/users/:id': UserPage,\r\n    '/posts/:postId/comments/:commentId': CommentPage,\r\n}, view)\r\n```\r\n\r\n### How It Works\r\n\r\n- All pages are created once and stay in DOM\r\n- Navigation toggles visibility via CSS classes (`.enter` / `.exit`)\r\n- Smooth transitions because both pages exist during animation\r\n- State preserved (scroll position, form inputs)\r\n\r\n### Page Component\r\n\r\nBase includes a `Page` component with built-in CSS transitions for smooth page navigation:\r\n\r\n```typescript\r\nimport { Page } from 'base/components/advanced/page'\r\nimport { IRouteEvent } from 'base/lib/router'\r\n\r\nexport const AboutPage = () => {\r\n    const page = Page()  // Comes with enter/exit animations\r\n    \r\n    // Add your content\r\n    page.append(header, body)\r\n    \r\n    // Uses default animations - slide up on enter, slide up on exit\r\n    return page\r\n}\r\n```\r\n\r\n**Default animations:**\r\n- Enter: Slides up from bottom, fades in\r\n- Exit: Slides up, fades out\r\n\r\n### Customizing Page Transitions\r\n\r\n```typescript\r\nimport { Page } from 'base/components/advanced/page'\r\nimport { IRouteEvent } from 'base/lib/router'\r\n\r\nexport const HomePage = () => {\r\n    const page = Page()\r\n    \r\n    // Override default enter - show instantly\r\n    page.off('enter')\r\n    page.on('enter', async ({ from }: IRouteEvent) => {\r\n        page.showInstant()\r\n        // Load data...\r\n    })\r\n    \r\n    return page\r\n}\r\n\r\nexport const AboutPage = () => {\r\n    const page = Page()\r\n    \r\n    // Custom exit - slide DOWN instead of up (for back navigation)\r\n    page.off('exit')\r\n    page.on('exit', async () => {\r\n        await page.exitDown()\r\n    })\r\n    \r\n    return page\r\n}\r\n```\r\n\r\n**Page methods:**\r\n- `showInstant()` - Show page without animation\r\n- `exitDown()` - Exit with downward slide (for back navigation)\r\n\r\n### Page Lifecycle Events\r\n\r\nPages receive `enter` and `exit` events from the router:\r\n\r\n```typescript\r\npage.on('enter', async ({ params, query, from, data }: IRouteEvent) => {\r\n    // params: { id: '123' } for /users/:id\r\n    // query: { sort: 'name' } for ?sort=name\r\n    // from: '/home' - previous path\r\n    // data: custom data from router.goto()\r\n})\r\n\r\npage.on('exit', async () => {\r\n    // Called when navigating away\r\n    // Async supported - router waits for completion\r\n})\r\n```\r\n\r\n### Navigation\r\n\r\n```typescript\r\nimport router from 'base/lib/router'\r\n\r\n// Navigate to path\r\nrouter.goto('/about')\r\n\r\n// With query params\r\nrouter.goto('/search?q=hello&page=2')\r\n\r\n// With custom data (available in enter event)\r\nrouter.goto('/users/123', { fromNotification: true })\r\n\r\n// Browser history\r\nrouter.back()\r\nrouter.forward()\r\n\r\n// Get current state\r\nrouter.getPath()               // '/users/123'\r\nrouter.getParams()             // { id: '123' }\r\nrouter.getQuery()              // { sort: 'name' }\r\nrouter.getQuery('sort')        // 'name'\r\n\r\n// For heavy pages - manually destroy to force recreation\r\nrouter.destroyPage('/heavy-page')\r\n```\r\n\r\n### Resetting Heavy Pages\r\n\r\nSince pages stay in DOM, you may want to reset state on exit for heavy pages:\r\n\r\n```typescript\r\npage.on('exit', () => {\r\n    // Reset scroll position\r\n    page.el.scrollTop = 0\r\n    \r\n    // Clear dynamic content\r\n    list.empty()\r\n    \r\n    // Reset form inputs\r\n    input.val('')\r\n})\r\n\r\n// Or completely destroy the page to force recreation on next visit:\r\npage.on('exit', () => {\r\n    router.destroyPage('/heavy-page')\r\n})\r\n\r\n### Listening to Route Changes\r\n\r\nFor global navigation handling:\r\n\r\n```typescript\r\nrouter.on('change', ({ path, params, query, from, data }) => {\r\n    // Update navigation UI\r\n    updateActiveMenuItem(path)\r\n    \r\n    // Analytics\r\n    trackPageView(path)\r\n    \r\n    // Show/hide back button\r\n    backButton.style({ display: from ? 'block' : 'none' })\r\n})\r\n```\r\n\r\n### Complete Example\r\n\r\n```typescript\r\n// app.ts\r\nimport { Div } from './base/components/native/div'\r\nimport router from './base/lib/router'\r\nimport { FIXED, EASE } from './base/helpers/style'\r\nimport { HomePage } from './pages/home'\r\nimport { AboutPage } from './pages/about'\r\nimport { UserPage } from './pages/user'\r\n\r\nconst view = Div()\r\nconst app = Div()\r\nconst nav = Div()\r\n\r\n// Navigation\r\nconst homeLink = Div('Home')\r\nhomeLink.on('click', () => router.goto('/'))\r\n\r\nconst aboutLink = Div('About')\r\naboutLink.on('click', () => router.goto('/about'))\r\n\r\nnav.style({ display: 'flex', gap: '20px', padding: '10px' })\r\nnav.append(homeLink, aboutLink)\r\n\r\napp.style({ ...FIXED })\r\napp.append(nav, view)\r\n\r\n// Configure routes\r\nrouter.routes({\r\n    '/': { page: HomePage, cache: true },\r\n    '/about': { page: AboutPage, cache: false },\r\n    '/users/:id': { page: UserPage, cache: true },\r\n}, view)\r\n\r\nexport default app\r\n```\r\n\r\n```typescript\r\n// pages/user/index.ts\r\nimport { Div } from '../../base/components/native/div'\r\nimport http from '../../base/lib/http'\r\n\r\nexport const UserPage = () => {\r\n    const page = Div()\r\n    const name = Div()\r\n    const email = Div()\r\n    \r\n    page.append(name, email)\r\n    \r\n    page.on('enter', async ({ params }) => {\r\n        const { data } = await http.get(`/api/users/${params.id}`)\r\n        name.text(data.name)\r\n        email.text(data.email)\r\n    })\r\n    \r\n    return page\r\n}\r\n```\r\n\r\n## HTTP Client\r\n\r\nBuilt-in fetch wrapper with request deduplication:\r\n\r\n```typescript\r\nimport http from 'base/lib/http'\r\n\r\n// GET (automatically deduplicated - same URL = same request)\r\nconst { status, data } = await http.get('/api/users')\r\n\r\n// POST, PUT, PATCH, DELETE\r\nawait http.post('/api/users', { name: 'John' })\r\nawait http.put('/api/users/1', { name: 'Jane' })\r\nawait http.patch('/api/users/1', { active: true })\r\nawait http.delete('/api/users/1')\r\n\r\n// With auth\r\nawait http.get('/api/me', { auth: 'my-token' })\r\n\r\n// Upload with progress\r\nawait http.upload('/api/upload', file, {\r\n    onProgress: (loaded, total) => {\r\n        console.log(`${Math.round(loaded/total * 100)}%`)\r\n    }\r\n})\r\n```\r\n\r\n## Storage\r\n\r\n### localStorage\r\n\r\n```typescript\r\nimport ldb from 'base/lib/ldb'\r\n\r\nldb.set('user', { name: 'John' })  // Auto JSON stringify\r\nldb.get('user')                     // Auto JSON parse\r\nldb.remove('user')\r\nldb.clear()\r\n```\r\n\r\n### IndexedDB\r\n\r\n```typescript\r\nimport createDB from 'base/lib/idb'\r\n\r\nconst db = createDB('my-app')\r\n\r\n// Create store (run once on app init)\r\nawait db.createStore('users', 1, { keyPath: 'id', indices: ['email'] })\r\n\r\n// CRUD\r\nawait db.save('users', { id: '1', name: 'John', email: 'john@example.com' })\r\nconst user = await db.get('users', '1')\r\nconst allUsers = await db.all('users')\r\nawait db.update('users', { id: '1', name: 'Jane' })\r\nawait db.delete('users', '1')\r\n\r\n// Query\r\nconst results = await db.find('users', { \r\n    index: 'email', \r\n    value: 'john@example.com',\r\n    limit: 10,\r\n    reverse: true\r\n})\r\n```\r\n\r\n### Global State\r\n\r\n```typescript\r\nimport state from 'base/lib/state'\r\n\r\n// Simple get/set\r\nstate.set('user', { name: 'John', id: 123 })\r\nstate.get('user')  // { name: 'John', id: 123 }\r\n\r\n// Subscribe to changes\r\nstate.on('user', (user) => {\r\n    console.log('User changed:', user)\r\n})\r\n\r\n// Subscribe to any change\r\nstate.on('change', ({ key, value }) => {\r\n    console.log(`${key} changed`)\r\n})\r\n```\r\n\r\n## Helpers\r\n\r\n### Style Helpers\r\n\r\n```typescript\r\nimport { ABSOLUTE, FIXED, FLEX, CENTER, HIDE, SHOW, EASE, WH, X, Y, SCALE, ROTATE } from 'base/helpers/style'\r\n\r\ncard.style({ ...ABSOLUTE })           // position: absolute; inset: 0;\r\ncard.style({ ...CENTER })             // display: flex; align-items: center; justify-content: center;\r\ncard.style({ ...EASE(0.3) })          // transition: all 0.3s ease;\r\ncard.style({ ...WH(100, 50) })        // width: 100px; height: 50px;\r\ncard.style({ ...Y(-10) })             // transform: translateY(-10px);\r\n```\r\n\r\n### Ripple Effect\r\n\r\n```typescript\r\nimport { withRipple } from 'base/utils/ripple'\r\nimport { Button } from 'base/components/native/button'\r\n\r\nconst btn = withRipple(Button('Click me'))\r\n// Now has Material Design ripple effect on click/touch\r\n```\r\n\r\n### Device Detection\r\n\r\n```typescript\r\nimport { isMobile, isTouch, isIOS, isAndroid } from 'base/helpers/device'\r\n\r\nif (isMobile()) { /* mobile layout */ }\r\nif (isTouch()) { /* touch interactions */ }\r\n```\r\n\r\n### Validation\r\n\r\n```typescript\r\nimport { isEmail, isUrl, isPhone, isStrongPassword } from 'base/helpers/regex'\r\n\r\nif (isEmail(input.value())) { /* valid */ }\r\n```\r\n\r\n## Project Structure\r\n\r\n```\r\nmy-app/\r\n├── base/                 # The framework (copy or npm install)\r\n├── components/           # Your reusable components\r\n│   └── card/\r\n│   └── index.module.css  # CSS module\r\n│   └── index.ts          # Component\r\n├── pages/                # Page components\r\n│   ├── home/\r\n│   │   └── index.ts\r\n│   └── about/\r\n│       └── index.ts\r\n├── services/             # API, state management\r\n├── styles/               # CSS files\r\n├── app.ts                # App setup with router\r\n├── index.ts              # Entry point\r\n└── index.html\r\n```\r\n\r\n## Philosophy\r\n\r\n1. **Imperative over declarative** - You control the DOM directly\r\n2. **Explicit over implicit** - No hidden state updates or re-renders\r\n3. **Composition over inheritance** - Mix capabilities with Object.assign\r\n4. **Simplicity over features** - Small API surface, easy to learn\r\n\r\n---\r\n\r\nMIT License\r\n","readmeFilename":"README.md"}