Lumino Documentation

repository·main·Indexed 19 days ago

https://github.com/jupyterlab/lumino

A toolkit of JavaScript/TypeScript packages for building complex, desktop-like web applications. Formerly known as PhosphorJS, Lumino provides the core UI primitives—including widgets, layouts (such as DockPanel), and events—that serve as the foundational UI framework for JupyterLab.

Tokens
99.8K
Snippets
344
Records
404
Agent score
67%

What's inside Lumino

  1. What is Lumino?

    main

    Lumino is a collection of TypeScript-based JavaScript packages that provide a toolkit for building high-performance, extensible, desktop-like web applications. It provides essential building blocks including:

    • Widgets: UI components for managing content.
    • Layouts: Systems for arranging widgets (e.g., DockPanel).
    • Events: A robust event system for application interaction.
    • Data Structures: Optimized structures for application state and data management.

    Lumino was formerly known as PhosphorJS and serves as the foundational UI framework for JupyterLab.

  2. Understand the architecture of example-dockpanel-amd

    main

    The example-dockpanel-amd package is an AMD (Asynchronous Module Definition) version of the standard example-dockpanel. It is designed for environments using RequireJS.

    Key characteristics:

    • Dependency Loading: All dependencies are loaded via AMD using RequireJS (check the <head> section of index.html for details).
    • Compatibility: TypeScript is compiled into IE-compatible JavaScript.
    • Styling: CSS files are manually imported as required via style/index.css.
  3. Use the Poll class for generic polling

    main

    The @lumino/polling package provides the Poll class for managing periodic tasks. It supports three different subscription patterns to handle poll ticks:

    1. Signal-based: Connect to the Poll#ticked signal (from @lumino/signaling) to react to every tick.
    2. Promise-based: Await the Poll#tick promise, which resolves after every tick and only rejects when the poll is disposed.
    3. AsyncIterable: Use for-await...of loops to iterate over poll states.

    Additionally, the package provides Debouncer and Throttler for rate limiting.

    import { Poll } from '@lumino/polling';
    
    // Basic configuration example
    const poll = new Poll({
      auto: false,
      factory: () => Promise.resolve(),
      frequency: { interval: 100, backoff: false }
    });
  4. Understand VirtualNode types

    main

    The VirtualNode type is a union of two specific structures:

    1. VirtualElement: Represents an HTML tag with attributes and children. It has a type: "element".
    2. VirtualText: Represents a raw text node. It has a type: "text" and contains a content string.

    This distinction allows the virtual DOM engine to differentiate between structural elements and leaf text nodes during the reconciliation process.

  5. Implement a DataModel

    main

    To provide data to a DataGrid, you must implement the abstract DataModel class. The DataGrid relies on this model to query dimensions and cell values.

    Required abstract methods to implement:

    • columnCount(region: DataModel.ColumnRegion): number
    • rowCount(region: DataModel.RowRegion): number
    • data(region: DataModel.CellRegion, row: number, column: number): any

    Useful model features:

    • Change Notifications: The changed signal emits ChangedArgs (e.g., rows-inserted, columns-moved, cells-changed) so the grid knows when to repaint.
    • Metadata: The metadata(region, row, column) method allows attaching arbitrary data to cells.
    • Grouping: group(region, groupIndex) and groupCount(region) allow for organized data structures.
  6. Customize cell editing with ICellEditor

    main

    To change how cells are edited, implement the ICellEditor interface. This allows you to control the editing lifecycle and response.

    Key Interfaces:

    • ICellEditor: Defines cancel() and edit(cell, options).
    • ICellEditOptions: Passed to the edit method. Includes editor, onCancel, onCommit (receives ICellEditResponse), and validator (ICellInputValidator).
    • ICellEditResponse: Contains the cell config, the value committed, and the cursorMovement direction.
    • ICellEditorController: Manages editors via setEditor(identifier, editor) and provides edit() and cancel() methods.
  7. Understand the AMD implementation details of the nested dock panel example

    main

    The example-nested-dockpanel-amd is a specialized version of the dock panel example with the following architectural characteristics:

    • Dependency Loading: All dependencies are loaded via AMD and RequireJS. You can find the specific loading configuration in the <head> section of index.html.
    • Compatibility: TypeScript source files are compiled into JavaScript compatible with Internet Explorer (IE).
    • Styling: CSS is not automatically injected by a bundler; instead, CSS files are manually imported as required via style/index.css.
  8. Understand the IRetroable interface

    main

    The IRetroable interface is used for objects that can be converted back into an IterableIterator. If an object implements retro(), you can use the retro() utility function to consume it as an iterable.

    import { retro, IRetroable } from '@lumino/algorithm';
    
    class MyCollection implements IRetroable<number> {
        *retro() {
            yield 1;
            yield 2;
        }
    }
    
    const collection = new MyCollection();
    const iterator = retro(collection);
    console.log([...iterator]); // [1, 2]
  9. How to provide and require services using PluginRegistry

    main

    The PluginRegistry uses Token objects to facilitate loose coupling between plugins. Instead of importing one plugin into another, plugins communicate through a shared Token that identifies a service.

    1. Define a shared Token

    Create a Token that both the provider and consumer will reference. This token acts as the unique identifier for the service.

    2. Provide a service

    A provider plugin must:

    • List the Token in its provides array.
    • Return the service instance from its activate method.

    3. Require a service

    A consumer plugin must:

    • List the Token in its requires array.
    • Accept the resolved service as an argument in its activate method.

    4. Register and activate

    Register both plugins with the PluginRegistry and call activate() to trigger the dependency resolution and startup sequence.

    // 1. The shared token
    import { Token } from '@lumino/coreutils';
    export const IGreeting = new Token<IGreeting>('igreeting');
    
    export interface IGreeting {
      sayHello(): void;
    }
    
    // 2. The provider plugin
    const helloPlugin = {
      provides: [IGreeting],
      activate: () => {
        return {
          sayHello: () => console.log('Hello! (from the hello plugin)')
        };
      }
    };
    
    // 3. The consumer plugin
    const greeterPlugin = {
      requires: [IGreeting],
      activate: (greeting: IGreeting) => {
        greeting.sayHello();
      }
    };
    
    // 4. Running it
    import { PluginRegistry } from '@lumino/coreutils';
    const registry = new PluginRegistry();
    registry.register(helloPlugin);
    registry.register(greeterPlugin);
    registry.activate();
  10. Manage keyboard layouts in @lumino/keyboard

    main

    The @lumino/keyboard package provides utilities for handling keyboard layouts and mapping KeyboardEvent objects to specific key strings. You can interact with the current layout using getKeyboardLayout() and setKeyboardLayout(layout).

    By default, the package provides an EN_US layout. You can implement custom layouts by satisfying the IKeyboardLayout interface or by using the KeycodeLayout class.

    import { getKeyboardLayout, setKeyboardLayout, EN_US } from '@lumino/keyboard';
    
    // Access the current layout
    const currentLayout = getKeyboardLayout();
    
    // Switch to the EN_US layout
    setKeyboardLayout(EN_US);
  11. Handle generator delegation with `yield*` carefully

    main

    When using yield* in generator functions to return the contents of another iterable, be aware that predicates or logic checks are evaluated at the time of iteration, not at the time the generator is initialized. This can lead to different behavior compared to returning an iterator directly.

    If your logic depends on a state that might change between the generator's creation and its consumption, returning the iterator directly (using Symbol.iterator()) or using empty() from @lumino/algorithm may be more predictable than yield*.

    import { empty } from '@lumino/algorithm';
    
    const source = [1, 2, 3, 4, 5];
    let flagged = false;
    
    function counter(): IterableIterator<number> {
      if (!flagged) {
        return source[Symbol.iterator]();
      }
      return empty();
    }
    
    // If flagged is set to true before iteration starts:
    const iterable = counter();
    flagged = true;
    console.log(Array.from(iterable)); // [1, 2, 3, 4, 5]
  12. Build and run the example-plugin-registry-server

    main

    To run the example-plugin-registry-server from the root of the repository, use the following commands to install dependencies, build the core packages, and then build and start the specific workspace:

    yarn install
    yarn run build
    yarn workspace @lumino/example-plugin-registry-server build
    yarn workspace @lumino/example-plugin-registry-server start