| 
    
      
     | 
    
      import { TraceSource } from "@implab/core-amd/log/TraceSource";
     | 
  
                        
    
    
         | 
    
      
     | 
    
      import { isPromise } from "@implab/core-amd/safe";
     | 
  
                        
    
    
         | 
    
      
     | 
    
      import { id as mid } from "module";
     | 
  
                        
    
    
         | 
    
      
     | 
    
      import { IScope, Scope } from "./Scope";
     | 
  
                        
    
    
         | 
    
      
     | 
    
      import { isNode, isRendition, isWidget } from "./traits";
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      const trace = TraceSource.get(mid);
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      interface Context {
     | 
  
                        
    
    
         | 
    
      
     | 
    
          readonly scope: IScope;
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
          readonly hooks?: (() => void)[];
     | 
  
                        
    
    
         | 
    
      
     | 
    
      }
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      let _context: Context = {
     | 
  
                        
    
    
         | 
    
      
     | 
    
          scope: Scope.dummy
     | 
  
                        
    
    
         | 
    
      
     | 
    
      };
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      let _renderCount = 0;
     | 
  
                        
    
    
         | 
    
      
     | 
    
      let _renderId = 1;
     | 
  
                        
    
    
         | 
    
      
     | 
    
      let _renderedHooks: (() => void)[] = [];
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      const guard = (cb: () => unknown) => {
     | 
  
                        
    
    
         | 
    
      
     | 
    
          try {
     | 
  
                        
    
    
         | 
    
      
     | 
    
              const result = cb();
     | 
  
                        
    
    
         | 
    
      
     | 
    
              if (isPromise(result)) {
     | 
  
                        
    
    
         | 
    
      
     | 
    
                  const warn = (ret: unknown) => trace.error("The callback {0} competed asynchronously. result = {1}", cb, ret);
     | 
  
                        
    
    
         | 
    
      
     | 
    
                  result.then(warn, warn);
     | 
  
                        
    
    
         | 
    
      
     | 
    
              }
     | 
  
                        
    
    
         | 
    
      
     | 
    
          } catch (e) {
     | 
  
                        
    
    
         | 
    
      
     | 
    
              trace.error(e);
     | 
  
                        
    
    
         | 
    
      
     | 
    
          }
     | 
  
                        
    
    
         | 
    
      
     | 
    
      };
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      /**
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * 
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * @param scope 
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * @returns 
     | 
  
                        
    
    
         | 
    
      
     | 
    
       */
     | 
  
                        
    
    
         | 
    
      
     | 
    
      export const beginRender = (scope = getScope()) => {
     | 
  
                        
    
    
         | 
    
      
     | 
    
          const prev = _context;
     | 
  
                        
    
    
         | 
    
      
     | 
    
          _renderCount++;
     | 
  
                        
    
    
         | 
    
      
     | 
    
          const renderId = _renderId++;
     | 
  
                        
    
    
         | 
    
      
     | 
    
          trace.debug("beginRender [{0}], pending = {1}", renderId, _renderCount);
     | 
  
                        
    
    
         | 
    
      
     | 
    
          if (_renderCount === 1)
     | 
  
                        
    
    
         | 
    
      
     | 
    
              onRendering();
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
          _context = {
     | 
  
                        
    
    
         | 
    
      
     | 
    
              scope,
     | 
  
                        
    
    
         | 
    
      
     | 
    
              hooks: []
     | 
  
                        
    
    
         | 
    
      
     | 
    
          };
     | 
  
                        
    
    
         | 
    
      
     | 
    
          return endRender(prev, _context, renderId);
     | 
  
                        
    
    
         | 
    
      
     | 
    
      };
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      /**
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * Method for a deferred rendering. Returns a promise with `beginRender()` function.
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * Call to `scheduleRender` will save the current context, and will increment pending
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * operations counter.
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * 
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * @example
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * 
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * const begin = await scheduleRender();
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * const end = begin();
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * try {
     | 
  
                        
    
    
         | 
    
      
     | 
    
       *     // do some DOM manipulations
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * } finally {
     | 
  
                        
    
    
         | 
    
      
     | 
    
       *     end();
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * }
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * 
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * @param scope 
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * @returns 
     | 
  
                        
    
    
         | 
    
      
     | 
    
       */
     | 
  
                        
    
    
         | 
    
      
     | 
    
      export const scheduleRender = async (scope = getScope()) => {
     | 
  
                        
    
    
         | 
    
      
     | 
    
          const prev = _context;
     | 
  
                        
    
    
         | 
    
      
     | 
    
          _renderCount++;
     | 
  
                        
    
    
         | 
    
      
     | 
    
          const renderId = _renderId ++;
     | 
  
                        
    
    
         | 
    
      
     | 
    
          trace.debug("scheduleRender [{0}], pending = {1}", renderId, _renderCount);
     | 
  
                        
    
    
         | 
    
      
     | 
    
          if (_renderCount === 1)
     | 
  
                        
    
    
         | 
    
      
     | 
    
              onRendering();
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
          await Promise.resolve();
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
          return () => {
     | 
  
                        
    
    
         | 
    
      
     | 
    
              trace.debug("beginRender [{0}], pending = {1}", renderId, _renderCount);
     | 
  
                        
    
    
         | 
    
      
     | 
    
              _context = {
     | 
  
                        
    
    
         | 
    
      
     | 
    
                  scope,
     | 
  
                        
    
    
         | 
    
      
     | 
    
                  hooks: []
     | 
  
                        
    
    
         | 
    
      
     | 
    
              };
     | 
  
                        
    
    
         | 
    
      
     | 
    
              return endRender(prev, _context, renderId);
     | 
  
                        
    
    
         | 
    
      
     | 
    
          };
     | 
  
                        
    
    
         | 
    
      
     | 
    
      };
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      /**
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * Completes render operation
     | 
  
                        
    
    
         | 
    
      
     | 
    
       */
     | 
  
                        
    
    
         | 
    
      
     | 
    
      const endRender = (prev: Context, current: Context, renderId: number) => () => {
     | 
  
                        
    
    
         | 
    
      
     | 
    
          if (_context !== current)
     | 
  
                        
    
    
         | 
    
      
     | 
    
              trace.error("endRender mismatched beginRender call");
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
          const { hooks } = _context;
     | 
  
                        
    
    
         | 
    
      
     | 
    
          if (hooks)
     | 
  
                        
    
    
         | 
    
      
     | 
    
              hooks.forEach(guard);
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
          _renderCount--;
     | 
  
                        
    
    
         | 
    
      
     | 
    
          _context = prev;
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
          trace.debug("endRender [{0}], pending = {1}", renderId, _renderCount);
     | 
  
                        
    
    
         | 
    
      
     | 
    
          if (_renderCount === 0)
     | 
  
                        
    
    
         | 
    
      
     | 
    
              onRendered();
     | 
  
                        
    
    
         | 
    
      
     | 
    
      };
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      // called when the first beginRender is called for this iteration
     | 
  
                        
    
    
         | 
    
      
     | 
    
      const onRendering = () => {
     | 
  
                        
    
    
         | 
    
      
     | 
    
          setTimeout(() => {
     | 
  
                        
    
    
         | 
    
      
     | 
    
              if (_renderCount !== 0)
     | 
  
                        
    
    
         | 
    
      
     | 
    
                  trace.error("Rendering tasks aren't finished, currently running = {0}", _renderCount);
     | 
  
                        
    
    
         | 
    
      
     | 
    
          });
     | 
  
                        
    
    
         | 
    
      
     | 
    
      };
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      // called when all render operations are complete
     | 
  
                        
    
    
         | 
    
      
     | 
    
      const onRendered = () => {
     | 
  
                        
    
    
         | 
    
      
     | 
    
          _renderedHooks.forEach(guard);
     | 
  
                        
    
    
         | 
    
      
     | 
    
          _renderedHooks = [];
     | 
  
                        
    
    
         | 
    
      
     | 
    
      };
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      export const whenRendered = () => new Promise<void>((resolve) => {
     | 
  
                        
    
    
         | 
    
      
     | 
    
          if (_renderCount)
     | 
  
                        
    
    
         | 
    
      
     | 
    
              _renderedHooks.push(resolve);
     | 
  
                        
    
    
         | 
    
      
     | 
    
          else
     | 
  
                        
    
    
         | 
    
      
     | 
    
              resolve();
     | 
  
                        
    
    
         | 
    
      
     | 
    
      });
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      export const renderHook = (hook: () => void) => {
     | 
  
                        
    
    
         | 
    
      
     | 
    
          const { hooks } = _context;
     | 
  
                        
    
    
         | 
    
      
     | 
    
          if (hooks)
     | 
  
                        
    
    
         | 
    
      
     | 
    
              hooks.push(hook);
     | 
  
                        
    
    
         | 
    
      
     | 
    
          else
     | 
  
                        
    
    
         | 
    
      
     | 
    
              guard(hook);
     | 
  
                        
    
    
         | 
    
      
     | 
    
      };
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      export const refHook = <T>(value: T, ref: JSX.Ref<T>) => {
     | 
  
                        
    
    
         | 
    
      
     | 
    
          const { hooks, scope } = _context;
     | 
  
                        
    
    
         | 
    
      
     | 
    
          if (hooks)
     | 
  
                        
    
    
         | 
    
      
     | 
    
              hooks.push(() => ref(value));
     | 
  
                        
    
    
         | 
    
      
     | 
    
          else
     | 
  
                        
    
    
         | 
    
      
     | 
    
              guard(() => ref(value));
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
          scope.own(() => ref(undefined));
     | 
  
                        
    
    
         | 
    
      
     | 
    
      };
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      /** Returns the current scope */
     | 
  
                        
    
    
         | 
    
      
     | 
    
      export const getScope = () => _context.scope;
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      /** Schedules the rendition to be rendered to the DOM Node
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * @param rendition The rendition to be rendered
     | 
  
                        
    
    
         | 
    
      
     | 
    
       * @param scope The scope
     | 
  
                        
    
    
         | 
    
      
     | 
    
       */
     | 
  
                        
    
    
         | 
    
      
     | 
    
      export const render = (rendition: unknown, scope = Scope.dummy) => {
     | 
  
                        
    
    
         | 
    
      
     | 
    
          const complete = beginRender(scope);
     | 
  
                        
    
    
         | 
    
      
     | 
    
          try {
     | 
  
                        
    
    
         | 
    
      
     | 
    
              return getItemDom(rendition);
     | 
  
                        
    
    
         | 
    
      
     | 
    
          } finally {
     | 
  
                        
    
    
         | 
    
      
     | 
    
              complete();
     | 
  
                        
    
    
         | 
    
      
     | 
    
          }
     | 
  
                        
    
    
         | 
    
      
     | 
    
      };
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     | 
  
                        
    
    
         | 
    
      
     | 
    
      /** Renders DOM element for different types of the argument. */
     | 
  
                        
    
    
         | 
    
      
     | 
    
      export const getItemDom = (v: unknown) => {
     | 
  
                        
    
    
         | 
    
      
     | 
    
          if (typeof v === "string" || typeof v === "number" || v instanceof RegExp || v instanceof Date) {
     | 
  
                        
    
    
         | 
    
      
     | 
    
              // primitive types converted to the text nodes
     | 
  
                        
    
    
         | 
    
      
     | 
    
              return document.createTextNode(v.toString());
     | 
  
                        
    
    
         | 
    
      
     | 
    
          } else if (isNode(v)) {
     | 
  
                        
    
    
         | 
    
      
     | 
    
              // nodes are kept as is
     | 
  
                        
    
    
         | 
    
      
     | 
    
              return v;
     | 
  
                        
    
    
         | 
    
      
     | 
    
          } else if (isRendition(v)) {
     | 
  
                        
    
    
         | 
    
      
     | 
    
              // renditions are instantiated
     | 
  
                        
    
    
         | 
    
      
     | 
    
              return v.getDomNode();
     | 
  
                        
    
    
         | 
    
      
     | 
    
          } else if (isWidget(v)) {
     | 
  
                        
    
    
         | 
    
      
     | 
    
              // widgets are converted to it's markup
     | 
  
                        
    
    
         | 
    
      
     | 
    
              return v.domNode;
     | 
  
                        
    
    
         | 
    
      
     | 
    
          } else if (typeof v === "boolean" || v === null || v === undefined) {
     | 
  
                        
    
    
         | 
    
      
     | 
    
              // null | undefined | boolean are removed
     | 
  
                        
    
    
         | 
    
      
     | 
    
              return document.createDocumentFragment();
     | 
  
                        
    
    
         | 
    
      
     | 
    
          } else if (v instanceof Array) {
     | 
  
                        
    
    
         | 
    
      
     | 
    
              // arrays will be translated to document fragments
     | 
  
                        
    
    
         | 
    
      
     | 
    
              const fragment = document.createDocumentFragment();
     | 
  
                        
    
    
         | 
    
      
     | 
    
              v.map(item => getItemDom(item))
     | 
  
                        
    
    
         | 
    
      
     | 
    
                  .forEach(node => fragment.appendChild(node));
     | 
  
                        
    
    
         | 
    
      
     | 
    
              return fragment;
     | 
  
                        
    
    
         | 
    
      
     | 
    
          } else {
     | 
  
                        
    
    
         | 
    
      
     | 
    
              // bug: explicit error otherwise
     | 
  
                        
    
    
         | 
    
      
     | 
    
              throw new Error(`Invalid parameter: ${String(v)}`);
     | 
  
                        
    
    
         | 
    
      
     | 
    
          }
     | 
  
                        
    
    
         | 
    
      
     | 
    
      };
     | 
  
                        
    
    
         | 
    
      
     | 
    
      
     |