bloom_js_native 0.3.7 copy "bloom_js_native: ^0.3.7" to clipboard
bloom_js_native: ^0.3.7 copied to clipboard

Reactive Dart web framework for real DOM apps — SSR, SSG, ISR-friendly rendering, signals, SEO, and browser APIs. Not Flutter web.

example/main.dart

import 'package:bloom_js_native/bloom_js_native.dart';
import 'package:bloom_js_native/browser.dart';

class TodoItem {
  final String id;
  final String title;
  final bool completed;

  const TodoItem({
    required this.id,
    required this.title,
    this.completed = false,
  });

  TodoItem copyWith({String? title, bool? completed}) => TodoItem(
        id: id,
        title: title ?? this.title,
        completed: completed ?? this.completed,
      );
}

enum TodoFilter { all, active, completed }

/// Counter + Todo demo for Bloom JS Native.
/// Compile with: dart compile js -O4 -o main.js main.dart
void main() {
  // ── State ──────────────────────────────────────────────────────────
  final count = signal(0);
  final todos = signal<List<TodoItem>>([
    const TodoItem(
        id: '1', title: 'Architect pure-Dart descriptor tree', completed: true),
    const TodoItem(
        id: '2',
        title: 'Mount fine-grained DOM with signals',
        completed: true),
    const TodoItem(
        id: '3', title: 'Ship Bloom JS Native M1-M3', completed: false),
  ]);
  final filter = signal(TodoFilter.all);
  final inputValue = signal('');

  // ── Derived ────────────────────────────────────────────────────────
  final doubled = computed(() => count.value * 2);
  final filteredTodos = computed(() {
    switch (filter.value) {
      case TodoFilter.all:
        return todos.value;
      case TodoFilter.active:
        return todos.value.where((t) => !t.completed).toList();
      case TodoFilter.completed:
        return todos.value.where((t) => t.completed).toList();
    }
  });
  final activeCount =
      computed(() => todos.value.where((t) => !t.completed).length);

  // ── Actions ────────────────────────────────────────────────────────
  void addTodo(BloomEvent e) {
    e.preventDefault();
    final v = inputValue.value.trim();
    if (v.isEmpty) return;
    todos.value = [
      ...todos.value,
      TodoItem(id: DateTime.now().millisecondsSinceEpoch.toString(), title: v),
    ];
    inputValue.value = '';
  }

  void toggleTodo(String id) {
    todos.value = [
      for (final t in todos.value)
        if (t.id == id) t.copyWith(completed: !t.completed) else t
    ];
  }

  void removeTodo(String id) {
    todos.value = todos.value.where((t) => t.id != id).toList();
  }

  // ── View ───────────────────────────────────────────────────────────
  final app = Div(
    className:
        'min-h-screen bg-[#09090B] text-zinc-100 font-sans antialiased selection:bg-indigo-500 selection:text-white',
    children: [
      Div(
        className: 'max-w-2xl mx-auto px-6 py-12 space-y-8',
        children: [
          // Header
          Header(
            className: 'border-b border-[#1E1E24] pb-6 space-y-2',
            children: [
              Div(
                className: 'flex items-center space-x-3',
                children: [
                  Div(
                    className:
                        'w-7 h-7 rounded-md bg-indigo-600 flex items-center justify-center font-bold text-white text-sm shadow-md shadow-indigo-500/20',
                    children: const [Text('B')],
                  ),
                  H1(
                    text: 'Bloom JS Native',
                    className:
                        'text-2xl font-bold tracking-tight text-zinc-100',
                  ),
                  Span(
                    text: 'v0.1.0',
                    className:
                        'px-2 py-0.5 text-xs font-mono bg-[#14141A] border border-[#27272A] rounded text-zinc-400',
                  ),
                ],
              ),
              P(
                text:
                    'Dart-owned reactivity with native DOM rendering and zero Flutter overhead.',
                className: 'text-sm text-zinc-400',
              ),
            ],
          ),

          // Metric / Counter Card
          Section(
            className:
                'p-5 bg-[#14141A] border border-[#27272A] rounded-xl space-y-4 shadow-xl',
            children: [
              Div(
                className: 'flex items-center justify-between',
                children: [
                  H2(
                      text: 'Reactive State Engine',
                      className:
                          'text-sm font-semibold text-zinc-300 uppercase tracking-wider'),
                  Span(
                      text: 'signals ^5.5.0',
                      className: 'text-xs text-indigo-400 font-mono'),
                ],
              ),
              Div(
                className: 'grid grid-cols-2 gap-4 py-2',
                children: [
                  Div(
                    className:
                        'p-3 bg-[#09090B] border border-[#1E1E24] rounded-lg',
                    children: [
                      P(
                          text: 'Primary Signal',
                          className: 'text-xs text-zinc-500'),
                      Live(() => P(
                          text: '${count.value}',
                          className:
                              'text-2xl font-bold text-zinc-100 font-mono')),
                    ],
                  ),
                  Div(
                    className:
                        'p-3 bg-[#09090B] border border-[#1E1E24] rounded-lg',
                    children: [
                      P(
                          text: 'Computed 2x',
                          className: 'text-xs text-zinc-500'),
                      Live(() => P(
                          text: '${doubled.value}',
                          className:
                              'text-2xl font-bold text-indigo-400 font-mono')),
                    ],
                  ),
                ],
              ),
              Div(
                className: 'flex items-center space-x-2',
                children: [
                  Button(
                    text: 'Decrement (-1)',
                    className:
                        'px-3 py-1.5 text-xs font-medium bg-[#1E1E24] hover:bg-[#27272A] text-zinc-300 border border-[#27272A] rounded-lg transition-colors cursor-pointer',
                    onClick: (_) => count.value--,
                  ),
                  Button(
                    text: 'Increment (+1)',
                    className:
                        'px-3 py-1.5 text-xs font-medium bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg shadow-sm shadow-indigo-600/30 transition-colors cursor-pointer',
                    onClick: (_) => count.value++,
                  ),
                  Button(
                    text: 'Reset',
                    className:
                        'px-3 py-1.5 text-xs font-medium text-zinc-400 hover:text-zinc-200 transition-colors cursor-pointer',
                    onClick: (_) => count.value = 0,
                  ),
                ],
              ),
              Show(
                () => count.value >= 10,
                child: Div(
                  className:
                      'px-3 py-2 bg-indigo-950/40 border border-indigo-800/50 rounded-lg text-xs text-indigo-300',
                  children: const [Text('Double digit milestone reached.')],
                ),
                fallback: Div(
                  className:
                      'px-3 py-2 bg-[#09090B]/60 border border-[#1E1E24] rounded-lg text-xs text-zinc-500',
                  children: const [
                    Text('Increment count to 10 to test Show condition.')
                  ],
                ),
              ),
            ],
          ),

          // Todo Task Section
          Section(
            className:
                'p-5 bg-[#14141A] border border-[#27272A] rounded-xl space-y-4 shadow-xl',
            children: [
              Div(
                className: 'flex items-center justify-between',
                children: [
                  H2(
                      text: 'Tasks & Fine-Grained DOM',
                      className:
                          'text-sm font-semibold text-zinc-300 uppercase tracking-wider'),
                  Live(() => Span(
                      text: '${activeCount.value} pending',
                      className: 'text-xs text-zinc-400 font-mono')),
                ],
              ),
              Form(
                onSubmit: addTodo,
                className: 'flex gap-2',
                children: [
                  Input(
                    placeholder: 'Create new task descriptor...',
                    className:
                        'flex-1 px-3.5 py-2 text-sm bg-[#09090B] border border-[#27272A] rounded-lg text-zinc-100 placeholder:text-zinc-600 focus:outline-none focus:border-indigo-500',
                    attrs: {'value': inputValue.value},
                    onInput: (e) => inputValue.value = e.value ?? '',
                  ),
                  Button(
                    text: 'Add Task',
                    className:
                        'px-4 py-2 text-sm font-medium bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg transition-colors cursor-pointer',
                    attrs: const {'type': 'submit'},
                  ),
                ],
              ),
              // Filters
              Div(
                className: 'flex space-x-1 border-b border-[#1E1E24] pb-2',
                children: [
                  for (final f in TodoFilter.values)
                    Live(() {
                      final isActive = filter.value == f;
                      return Button(
                        text: f.name.toUpperCase(),
                        className:
                            'px-2.5 py-1 text-xs font-mono rounded cursor-pointer ${isActive ? 'bg-indigo-600/20 text-indigo-400 border border-indigo-500/30' : 'text-zinc-500 hover:text-zinc-300'}',
                        onClick: (_) => filter.value = f,
                      );
                    }),
                ],
              ),
              // List
              Ul(
                className: 'space-y-2 pt-1',
                children: [
                  ForEach<TodoItem>(
                    () => filteredTodos.value,
                    (TodoItem t) => Li(
                      className:
                          'flex items-center justify-between p-3 bg-[#09090B] border border-[#1E1E24] hover:border-[#27272A] rounded-lg transition-colors',
                      children: [
                        Div(
                          className:
                              'flex items-center space-x-3 cursor-pointer',
                          onClick: (_) => toggleTodo(t.id),
                          children: [
                            Span(
                              text: t.title,
                              className:
                                  'text-sm ${t.completed ? 'line-through text-zinc-500' : 'text-zinc-200'}',
                            ),
                          ],
                        ),
                        Button(
                          text: 'Delete',
                          className:
                              'text-xs text-zinc-500 hover:text-red-400 cursor-pointer px-2 py-1',
                          onClick: (_) => removeTodo(t.id),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ],
          ),

          // Footer
          Footer(
            className:
                'pt-4 border-t border-[#1E1E24] text-center text-xs text-zinc-600',
            children: const [
              Text(
                  'Bloom Framework — Pure Dart Descriptors + Direct Browser DOM'),
            ],
          ),
        ],
      ),
    ],
  );

  mount(app, '#app');
}
0
likes
150
points
641
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Reactive Dart web framework for real DOM apps — SSR, SSG, ISR-friendly rendering, signals, SEO, and browser APIs. Not Flutter web.

Repository (GitHub)
View/report issues

Topics

#bloom #web #dom #signals #ssr

License

MIT (license)

Dependencies

http, meta, signals_core, web

More

Packages that depend on bloom_js_native