...
// Renders:
```
## Style and Class Names
```tsx
// className for CSS classes
...
// style for inline styles (as a string)
...
```
## Assertions (`asserts.ts`)
```typescript
import { checkArgument, checkExists, checkState, checkExhaustive, exists } from 'external/dev_april_corgi+/js/common/asserts';
// Throw if condition is false
checkArgument(value > 0, 'Value must be positive');
checkState(this.initialized, 'Not initialized');
// Throw if null/undefined, otherwise return value
const item = checkExists(maybeItem, 'Item not found');
// Exhaustiveness check for switch/if-else (compile-time check)
switch (value.kind) {
case 'a': return handleA();
case 'b': return handleB();
default: checkExhaustive(value); // Compile error if cases missed
}
// Type guard for null/undefined
if (exists(maybeValue)) {
// maybeValue is now non-null
}
```
## Futures (`futures.ts`)
Enhanced promises with synchronous completion checking:
```typescript
import { Future, asFuture, resolvedFuture, rejectedFuture, unsettledFuture } from 'external/dev_april_corgi+/js/common/futures';
// Wrap a promise
const future = asFuture(somePromise);
// Create pre-resolved/rejected futures
const resolved = resolvedFuture(value);
const rejected = rejectedFuture(error);
// Check completion synchronously
if (future.finished) {
if (future.ok) {
const value = future.value();
} else {
const error = future.error();
}
}
```
## Debouncer (`debouncer.ts`)
```typescript
import { Debouncer } from 'external/dev_april_corgi+/js/common/debouncer';
const debouncer = new Debouncer(300, () => {
// Called after 300ms of no triggers
});
// Trigger (resets timer if called again within delay)
await debouncer.trigger();
```
## Timer (`timer.ts`)
Repeating timer that can be started/stopped:
```typescript
import { Timer } from 'external/dev_april_corgi+/js/common/timer';
const timer = new Timer(1000, () => {
// Called every 1000ms
});
timer.start(); // Start repeating
timer.stop(); // Stop
timer.dispose(); // Cleanup
```
## Collections (`collections.ts`)
```typescript
import { DefaultMap, HashMap, HashSet, IdentitySetMultiMap, getOnlyElement, getFirstElement } from 'external/dev_april_corgi+/js/common/collections';
// Map with auto-initialization
const map = new DefaultMap
(() => []);
map.get('key').push(1); // No need to check if key exists
// Map/Set with custom hash function
const hashMap = new HashMap(key => key.id);
const hashSet = new HashSet(val => val.id);
// Multi-value map with identity comparison
const multiMap = new IdentitySetMultiMap();
multiMap.put('key', obj1);
multiMap.put('key', obj2);
// Get single element from iterable (throws if not exactly one)
const only = getOnlyElement(iterable);
const first = getFirstElement(iterable);
```
## Comparisons (`comparisons.ts`)
```typescript
import { deepEqual, approxEqual, approxGtOrEqual, approxLtOrEqual } from 'external/dev_april_corgi+/js/common/comparisons';
// Deep equality (handles objects, arrays, Maps, Sets, Dates, RegExp)
if (deepEqual(obj1, obj2)) { }
// Approximate numeric comparisons
if (approxEqual(a, b, 0.001)) { }
if (approxGtOrEqual(a, b, 0.001)) { }
```
## Arrays (`arrays.ts`)
```typescript
import { compare, equals, pushInto } from 'external/dev_april_corgi+/js/common/arrays';
// Lexicographic comparison (-1, 0, 1)
const cmp = compare(arr1, arr2);
// Shallow equality
if (equals(arr1, arr2)) { }
// Efficient push without stack issues
pushInto(destination, source);
```
## Promises (`promises.ts`)
```typescript
import { waitMs, waitSettled, waitTicks } from 'external/dev_april_corgi+/js/common/promises';
await waitMs(1000); // Wait 1 second
await waitSettled(); // Wait for microtask queue to settle
await waitTicks(10); // Wait N promise ticks
```
## Math (`math.ts`)
```typescript
import { clamp, floatCoalesce } from 'external/dev_april_corgi+/js/common/math';
const clamped = clamp(value, 0, 100); // Clamp between min/max
// Get first valid number from list
const num = floatCoalesce(maybeNum1, maybeNum2, defaultNum);
```
## Memoized (`memoized.ts`)
```typescript
import { Memoized, maybeMemoized } from 'external/dev_april_corgi+/js/common/memoized';
// Lazy-initialized value
const lazy = new Memoized(() => expensiveComputation());
console.log(lazy.value); // Computed once, cached
// SSR-aware memoization (doesn't cache on server)
const ssrSafe = maybeMemoized(() => computation());
```
## Complete Example
```tsx
import * as corgi from 'external/dev_april_corgi+/js/corgi';
import { Controller, Response } from 'external/dev_april_corgi+/js/corgi/controller';
import { declareEvent, CorgiEvent } from 'external/dev_april_corgi+/js/corgi/events';
import { EmptyDeps } from 'external/dev_april_corgi+/js/corgi/deps';
import { Button } from 'external/dev_april_corgi+/js/emu/button';
import { Input } from 'external/dev_april_corgi+/js/emu/input';
import { ACTION, CHANGED } from 'external/dev_april_corgi+/js/emu/events';
// Declare custom event
const TODO_ADDED = declareEvent<{ text: string }>('app.todoAdded');
// State interface
interface State {
todos: string[];
inputValue: string;
}
// Controller
class TodoController extends Controller<{}, EmptyDeps, HTMLDivElement, State> {
handleInputChange(e: CorgiEvent): void {
this.updateState({
...this.state,
inputValue: e.detail.value,
});
}
handleAddTodo(): void {
if (this.state.inputValue.trim()) {
this.updateState({
todos: [...this.state.todos, this.state.inputValue],
inputValue: '',
});
this.trigger(TODO_ADDED, { text: this.state.inputValue });
}
}
}
// Component
function TodoApp(props: {}, state: State | undefined, updateState: (s: State) => void) {
if (!state) {
state = { todos: [], inputValue: '' };
}
return (
);
}
// Bootstrap
if (process.env.CORGI_FOR_BROWSER) {
corgi.hydrateElement(checkExists(document.getElementById('root')), );
}
```
## Best Practices
1. **Initialize state in components**: Always check `if (!state)` and initialize
2. **Use `checkExists`**: Instead of `!` assertions for null checks
3. **Use refs for controller dependencies**: Name child controllers with `ref` for injection
4. **Prefer unboundEvents for simple handlers**: When a child doesn't need its own controller
5. **Trigger custom events for component communication**: Use `declareEvent` and `this.trigger()`
6. **Use custom events for child-to-parent communication**: Don't pass callbacks as props
7. **Register cleanup with registerDisposer**: Prevent memory leaks
8. **Use registerListener for window/document events**: Auto-cleanup on disposal
9. **Debounce state updates**: `updateState` is already debounced, but batch related changes
10. **Type your state and args**: Use TypeScript interfaces for type safety
11. **Type your controllers**: Use all four generic parameters `Controller`
12. **Use `render: 'wakeup'` event**: To run initialization code after DOM is ready