{"mappings":"AACA,cAAc,uBAAuB,sBAA2B;AAEhE,SAAS,UAAU,WAAiB;AAEpC,SAAS,mBAAmB;UAElB,WAAW;CACnB;CACA,QAAQ;CACR;;UAGQ,oBAAoB,WAAW;CACvC;;UAGQ,UAAU;OACZ;CACN;CACA,UAAU,QAAQ;CAClB;;AAGF,cAAM;UACJ,OAAO;UACP,SAAS;UACT,QAAQ;;KAGL,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;AA0B/B,cAMM,eAAe,YAAY;CAC/B,OAAO,iBAAiB,IAAI;CAE5B,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;;;;CAKR,AACA,qBAAqB;;;;CAKrB,SAAS;;;;CAKT,AACA;;;;CAKA,AACA;;;;CAKA,AACA,SAAS;;;;;;;;CAST,AACA,MAAM;;;;;;CAON,AACA;CAEA,IACI,OAAO,OAKG;CAAd,IAAI,UAAU;CAId;CAIA,UAAU;CAsBV;CAcA;CAKA,aAAa;CASb,UAAU,QAAQ,mBAAmB;;;;CAcrC,eAAe;;;;CA2Bf,iBAAiB,iBAAiB,eAAe;;;;CAwBjD;;;;CAWA,mBAAmB,cAAc,KAAK;CAQtC,OAAO;CAMP,OAAO,mBAAmB;CAI1B;;AAKF,eAAe;AACf,SAAS","names":[],"sources":["../../../../src/web-components/router/component.ts"],"sourcesContent":["import { godown, htmlSlot, styles } from \"@godown/element\";\nimport { type PropertyValueMap, type TemplateResult, css } from \"lit\";\nimport { property, state } from \"lit/decorators.js\";\nimport { Router as Mux, omit } from \"sharekit\";\n\nimport { GlobalStyle } from \"../../internal/global-style.js\";\n\ninterface RouteState {\n  pathname: string;\n  params: Record<string, string>;\n  path: string;\n}\n\ninterface RouteResult extends RouteState {\n  component: unknown;\n}\n\ninterface RouteItem {\n  [key: PropertyKey]: unknown;\n  path: string;\n  render?: (state?: RouteState) => unknown;\n  component?: unknown;\n}\n\nconst routerTypes = {\n  field: \"field\",\n  slotted: \"slotted\",\n  united: \"united\",\n} as const;\n\ntype RouterType = keyof typeof routerTypes;\n\nconst protoName = \"router\";\n\n/**\n * {@linkcode Router} has basic routing control.\n *\n * To switch routes, use `router-link component`.\n *\n * It has two methods to collect routes.\n *\n * 1. From field `routes`, an array, each elements require \"path\".\n * 2. From child elements, which have the slot attribute for matching routes.\n *\n * If only the method 1 is used, set `type` to `\"field\"`.\n *\n * If only the method 2 is used, set `type` to `\"slotted\"`.\n *\n * `type` defaults to `\"united\"`, which will try method 1, then method 2.\n *\n * If no routes are matched, the default value (no named slot) will be rendered.\n *\n * @slot - Display slot when there is no match.\n * @slot * - Matching slot will be displayed.\n * @category navigation\n */\n@godown(protoName)\n@styles(css`\n  :host {\n    display: contents;\n  }\n`)\nclass Router extends GlobalStyle {\n  static routerInstances: Set<Router> = new Set<Router>();\n\n  private __fieldRoute: Mux = new Mux();\n  private __slottedRoute: Mux = new Mux();\n  private __cacheRecord = new Map<string, RouteResult>();\n  private __routes: RouteItem[];\n\n  /**\n   * Render result.\n   */\n  @state()\n  component: unknown | TemplateResult = null;\n\n  /**\n   * Dynamic parameters record.\n   */\n  params?: Record<string, string>;\n\n  /**\n   * Value of matched path in routes.\n   */\n  @state()\n  path?: string;\n\n  /**\n   * Current pathname (equals to location.pathname).\n   */\n  @property()\n  pathname: string = location.pathname;\n\n  /**\n   * Rendered content when there is no match.\n   */\n  @state()\n  default: TemplateResult = htmlSlot();\n\n  /**\n   * The type of routing sources.\n   *\n   * If field, it won't collect the slot attribute of the child elements.\n   *\n   * This property should not be changed after the rendering is complete.\n   */\n  @property()\n  type: RouterType = routerTypes.united;\n\n  /**\n   * Cache accessed records.\n   *\n   * Emptied at each re-collection.\n   */\n  @property({ type: Boolean })\n  cache = false;\n\n  @state()\n  set routes(value) {\n    this.__routes = value;\n    this.collectFieldRoutes(value);\n  }\n\n  get routes(): RouteItem[] {\n    return this.__routes;\n  }\n\n  clear(): void {\n    this.__cacheRecord.clear();\n  }\n\n  protected render(): unknown {\n    let cached: RouteResult | undefined;\n    if (this.cache && (cached = this.__cacheRecord.get(this.pathname))) {\n      this.component = cached.component;\n      this.path = cached.path;\n      this.pathname = cached.pathname;\n    }\n    if (!cached) {\n      switch (this.type) {\n        case routerTypes.field:\n          this.component = this.fieldComponent();\n          break;\n        case routerTypes.slotted:\n          this.component = this.slottedComponent();\n          break;\n        default:\n          this.component = this.fieldComponent() ?? this.slottedComponent();\n      }\n    }\n    return this.component ?? this.default;\n  }\n\n  connectedCallback(): void {\n    super.connectedCallback();\n    Router.routerInstances.add(this);\n\n    if (this.type !== \"field\") {\n      this.observers.add(this, MutationObserver, this.collectSlottedRoutes, {\n        attributes: true,\n        attributeFilter: [\"slot\"],\n        subtree: true,\n      });\n      this.collectSlottedRoutes();\n    }\n  }\n\n  disconnectedCallback(): void {\n    super.disconnectedCallback();\n    Router.routerInstances.delete(this);\n  }\n\n  useRouter(): RouteResult {\n    return {\n      pathname: this.pathname,\n      params: this.params,\n      path: this.path,\n      component: this.component,\n    };\n  }\n\n  protected updated(changedProperties: PropertyValueMap<this>): void {\n    const shouldDispatch = changedProperties.has(\"pathname\") || changedProperties.has(\"path\");\n    if (shouldDispatch) {\n      const ur = this.useRouter();\n      if (!this.__cacheRecord.has(this.pathname) && this.path) {\n        this.__cacheRecord.set(this.pathname, ur);\n      }\n      this.dispatchCustomEvent(\"change\", ur);\n    }\n  }\n\n  /**\n   * Get component from {@linkcode routes} by query.\n   */\n  fieldComponent(query?: string): unknown {\n    if (!query) {\n      const mux = this.__fieldRoute.search(this.pathname);\n      this.params = mux.params(this.pathname);\n      query = mux.pattern;\n    }\n    this.path = query;\n\n    if (!query) {\n      return null;\n    }\n\n    const route = this.routes.find((r) => r.path === query);\n    if (!route) {\n      return null;\n    }\n\n    if (\"render\" in route) {\n      return route.render?.(omit(this.useRouter(), \"component\")) || null;\n    }\n\n    return route.component;\n  }\n\n  /**\n   * Get component from slotted elements by query.\n   */\n  slottedComponent(query?: string): TemplateResult<1> {\n    const slottedPaths = this._slottedNames;\n    if (!query) {\n      const mux = this.__fieldRoute.search(this.pathname);\n      this.params = mux.params(this.pathname);\n      query = mux.pattern;\n    }\n    this.path = query;\n\n    if (!query) {\n      return null;\n    }\n\n    this.path = slottedPaths.find((s) => s === query);\n    if (!this.path) {\n      return null;\n    }\n\n    return htmlSlot(this.path);\n  }\n\n  /**\n   * Reset the route tree, clear cache, collect routes from child elements.\n   */\n  collectSlottedRoutes(): void {\n    this.__slottedRoute = new Mux();\n    this.clear();\n    this._slottedNames.forEach((slotName) => {\n      this.__slottedRoute.insert(slotName);\n    });\n  }\n\n  /**\n   * Reset the route tree, clear cache, collect routes from value.\n   */\n  collectFieldRoutes(value: typeof this.routes): void {\n    this.__fieldRoute = new Mux();\n    this.clear();\n    value.forEach(({ path }) => {\n      this.__fieldRoute.insert(path);\n    });\n  }\n\n  static updateAll(): void {\n    this.routerInstances.forEach((i) => {\n      i.handlePopstate();\n    });\n  }\n\n  search(pathname: string): Mux {\n    return this.__fieldRoute.search(pathname) || this.__slottedRoute.search(pathname);\n  }\n\n  handlePopstate: () => void = this.events.add(window, \"popstate\", () => {\n    this.pathname = location.pathname;\n  });\n}\n\nexport default Router;\nexport { Router };\n"],"version":3,"file":"component.d.ts"}