@angular-wave/angular.ts 0.7.7 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (376) hide show
  1. package/@types/animations/raf-scheduler.d.ts +2 -2
  2. package/@types/animations/shared.d.ts +0 -1
  3. package/@types/core/compile/attributes.d.ts +3 -3
  4. package/@types/core/compile/compile.d.ts +1 -1
  5. package/@types/core/di/injector.d.ts +0 -1
  6. package/@types/core/di/internal-injector.d.ts +1 -0
  7. package/@types/core/di/ng-module.d.ts +5 -0
  8. package/@types/core/filter/filter.d.ts +11 -13
  9. package/@types/core/parse/parse.d.ts +6 -7
  10. package/@types/core/sanitize/sanitize-uri.d.ts +3 -6
  11. package/@types/core/scope/scope.d.ts +1 -1
  12. package/@types/directive/attrs/attrs.d.ts +7 -1
  13. package/@types/directive/bind/bind.d.ts +2 -1
  14. package/@types/directive/events/events.d.ts +9 -3
  15. package/@types/directive/http/http.d.ts +6 -2
  16. package/@types/directive/include/include.d.ts +2 -2
  17. package/@types/directive/input/input.d.ts +2 -12
  18. package/@types/directive/messages/messages.d.ts +9 -48
  19. package/@types/directive/model/model.d.ts +3 -3
  20. package/@types/directive/options/options.d.ts +13 -20
  21. package/@types/directive/switch/switch.d.ts +1 -0
  22. package/@types/directive/transclude/transclude.d.ts +10 -6
  23. package/@types/index.d.ts +1 -1
  24. package/@types/interface.d.ts +56 -18
  25. package/@types/{public.d.ts → ng.d.ts} +2 -2
  26. package/@types/router/directives/view-directive.d.ts +2 -19
  27. package/@types/router/{common → glob}/glob.d.ts +5 -1
  28. package/@types/router/globals.d.ts +2 -3
  29. package/@types/router/path/path-utils.d.ts +8 -11
  30. package/@types/router/state/interface.d.ts +1 -1
  31. package/@types/router/state/state-object.d.ts +1 -1
  32. package/@types/router/state/state-registry.d.ts +1 -2
  33. package/@types/router/state/state-service.d.ts +8 -7
  34. package/@types/router/state-filters.d.ts +24 -2
  35. package/@types/router/transition/transition.d.ts +12 -15
  36. package/@types/router/url/url-matcher.d.ts +3 -3
  37. package/@types/router/url/url-rule.d.ts +1 -0
  38. package/@types/router/url/url-rules.d.ts +26 -6
  39. package/@types/router/url/url-service.d.ts +30 -42
  40. package/@types/services/anchor-scroll.d.ts +1 -1
  41. package/@types/{core → services/exception}/exception-handler.d.ts +4 -4
  42. package/@types/{core/error-handler.d.ts → services/exception/interface.d.ts} +1 -1
  43. package/@types/services/http/http.d.ts +48 -3
  44. package/@types/services/http/interface.d.ts +2 -2
  45. package/@types/services/http-backend/http-backend.d.ts +49 -44
  46. package/@types/services/location/interface.d.ts +63 -0
  47. package/@types/services/location/location.d.ts +330 -0
  48. package/@types/{core → services}/sce/sce.d.ts +1 -1
  49. package/@types/services/template-cache/interface.d.ts +8 -2
  50. package/@types/services/template-cache/template-cache.d.ts +1 -1
  51. package/@types/services/template-request.d.ts +1 -1
  52. package/@types/shared/cache.d.ts +0 -2
  53. package/@types/shared/common.d.ts +0 -2
  54. package/@types/shared/dom.d.ts +6 -0
  55. package/@types/shared/interface.d.ts +0 -4
  56. package/@types/{router/common → shared}/queue.d.ts +2 -2
  57. package/@types/shared/test-utils.d.ts +1 -0
  58. package/@types/shared/url-utils/interface.d.ts +46 -0
  59. package/@types/shared/url-utils/url-utils.d.ts +64 -0
  60. package/@types/shared/utils.d.ts +44 -6
  61. package/Makefile +8 -4
  62. package/dist/angular-ts.esm.js +1889 -2199
  63. package/dist/angular-ts.umd.js +1889 -2199
  64. package/dist/angular-ts.umd.min.js +1 -1
  65. package/docs/assets/scss/index.scss +23 -0
  66. package/docs/content/_index.md +9 -8
  67. package/docs/content/docs/_index.md +1 -1
  68. package/docs/content/docs/directive/app.md +1 -1
  69. package/docs/content/docs/directive/bind.md +10 -8
  70. package/docs/content/docs/directive/blur.md +1 -1
  71. package/docs/content/docs/directive/channel.md +2 -2
  72. package/docs/content/docs/directive/class-even.md +1 -1
  73. package/docs/content/docs/directive/class-odd.md +1 -1
  74. package/docs/content/docs/directive/class.md +1 -1
  75. package/docs/content/docs/directive/click.md +1 -1
  76. package/docs/content/docs/directive/copy.md +1 -1
  77. package/docs/content/docs/directive/cut.md +1 -1
  78. package/docs/content/docs/directive/dblclick.md +1 -1
  79. package/docs/content/docs/directive/focus.md +1 -1
  80. package/docs/content/docs/directive/get.md +203 -0
  81. package/docs/content/docs/directive/keydown.md +1 -1
  82. package/docs/content/docs/directive/keyup.md +1 -1
  83. package/docs/content/docs/directive/load.md +1 -1
  84. package/docs/content/docs/directive/mousedown.md +1 -1
  85. package/docs/content/docs/directive/mouseenter.md +1 -1
  86. package/docs/content/docs/directive/mouseleave.md +1 -1
  87. package/docs/content/docs/directive/mousemove.md +1 -1
  88. package/docs/content/docs/directive/mouseout.md +1 -1
  89. package/docs/content/docs/directive/mouseover.md +1 -1
  90. package/docs/content/docs/directive/mouseup.md +1 -1
  91. package/docs/content/docs/directive/non-bindable.md +28 -0
  92. package/docs/content/docs/provider/locationProvider.md +26 -0
  93. package/docs/content/docs/provider/templateCacheProvider.md +66 -1
  94. package/docs/content/docs/service/location.md +57 -0
  95. package/docs/content/docs/service/templateCache.md +2 -2
  96. package/docs/content/docs/service/url.md +5 -0
  97. package/docs/layouts/partials/hooks/head-end.html +1 -1
  98. package/docs/layouts/shortcodes/showcss.html +2 -0
  99. package/docs/layouts/shortcodes/version.html +1 -0
  100. package/docs/static/examples/counter/counter-test.html +0 -4
  101. package/docs/static/examples/eventbus/eventbus-test.html +0 -4
  102. package/docs/static/examples/ng-bind/ng-bind.html +2 -2
  103. package/docs/static/examples/ng-non-bindable/ng-non-bindable-test.html +13 -0
  104. package/docs/static/examples/ng-non-bindable/ng-non-bindable.html +3 -0
  105. package/docs/static/examples/ng-non-bindable/ng-non-bindable.test.js +11 -0
  106. package/docs/static/typedoc/assets/hierarchy.js +1 -1
  107. package/docs/static/typedoc/assets/highlight.css +6 -6
  108. package/docs/static/typedoc/assets/navigation.js +1 -1
  109. package/docs/static/typedoc/assets/search.js +1 -1
  110. package/docs/static/typedoc/classes/Location.html +55 -0
  111. package/docs/static/typedoc/classes/LocationProvider.html +20 -0
  112. package/docs/static/typedoc/classes/NgModule.html +32 -0
  113. package/docs/static/typedoc/classes/TemplateCacheProvider.html +1 -1
  114. package/docs/static/typedoc/hierarchy.html +1 -1
  115. package/docs/static/typedoc/index.html +1 -1
  116. package/docs/static/typedoc/interfaces/DefaultPorts.html +5 -0
  117. package/docs/static/typedoc/interfaces/Directive.html +5 -4
  118. package/docs/static/typedoc/interfaces/Html5Mode.html +23 -0
  119. package/docs/static/typedoc/interfaces/HttpProviderDefaults.html +1 -1
  120. package/docs/static/typedoc/interfaces/HttpResponse.html +2 -3
  121. package/docs/static/typedoc/interfaces/Provider.html +16 -10
  122. package/docs/static/typedoc/interfaces/RequestConfig.html +1 -1
  123. package/docs/static/typedoc/interfaces/RequestShortcutConfig.html +1 -1
  124. package/docs/static/typedoc/interfaces/TemplateCache.html +7 -0
  125. package/docs/static/typedoc/interfaces/UrlParts.html +9 -0
  126. package/docs/static/typedoc/types/AnnotatedDirectiveFactory.html +1 -0
  127. package/docs/static/typedoc/types/AnnotatedFactory.html +1 -1
  128. package/docs/static/typedoc/types/DirectiveFactory.html +1 -2
  129. package/docs/static/typedoc/types/DirectiveFactoryFn.html +1 -0
  130. package/docs/static/typedoc/types/Expression.html +1 -1
  131. package/docs/static/typedoc/types/HttpResponseStatus.html +1 -0
  132. package/docs/static/typedoc/types/{TemplateCache.html → SwapModeType.html} +1 -1
  133. package/docs/static/typedoc/types/UrlChangeListener.html +5 -0
  134. package/docs/static/typedoc/variables/SwapMode.html +11 -0
  135. package/docs/static/version.js +13 -0
  136. package/docs/test-results/.last-run.json +4 -0
  137. package/docs/test-results/static-examples-counter-counter-counter-example/error-context.md +50 -0
  138. package/legacy.d.ts +0 -10
  139. package/package.json +1 -3
  140. package/src/{loader.js → angular.js} +5 -10
  141. package/src/angular.spec.js +189 -21
  142. package/src/animations/animate-children-directive.js +2 -2
  143. package/src/animations/animate-css.js +17 -18
  144. package/src/animations/animate.spec.js +1 -1
  145. package/src/animations/raf-scheduler.js +1 -1
  146. package/src/animations/shared.js +2 -12
  147. package/src/binding.spec.js +1 -1
  148. package/src/core/compile/attributes.js +1 -1
  149. package/src/core/compile/compile.js +7 -10
  150. package/src/core/compile/compile.spec.js +1 -1
  151. package/src/core/controller/controller.spec.js +1 -1
  152. package/src/core/controller/controller.test.js +1 -0
  153. package/src/core/di/injector.js +11 -25
  154. package/src/core/di/injector.spec.js +2 -2
  155. package/src/core/di/injector.test.js +2 -2
  156. package/src/core/di/internal-injector.js +7 -7
  157. package/src/core/di/ng-module.js +12 -27
  158. package/src/core/filter/filter.js +28 -28
  159. package/src/core/filter/filter.spec.js +1 -1
  160. package/src/core/filter/filter.test.js +1 -0
  161. package/src/core/interpolate/interpolate.js +4 -6
  162. package/src/core/interpolate/interpolate.spec.js +1 -1
  163. package/src/core/interpolate/interpolate.test.js +1 -0
  164. package/src/core/parse/ast/ast.spec.js +1 -1
  165. package/src/core/parse/ast/ast.test.js +1 -1
  166. package/src/core/parse/interpreter.js +32 -38
  167. package/src/core/parse/lexer/lexer.spec.js +1 -1
  168. package/src/core/parse/parse.js +150 -146
  169. package/src/core/parse/parse.spec.js +17 -16
  170. package/src/core/prop.spec.js +1 -1
  171. package/src/core/root-element.spec.js +1 -1
  172. package/src/core/sanitize/sanitize-uri.js +3 -3
  173. package/src/core/scope/scope.js +12 -13
  174. package/src/core/scope/scope.spec.js +3 -4
  175. package/src/directive/aria/aria.spec.js +1 -1
  176. package/src/directive/aria/aria.test.js +1 -0
  177. package/src/directive/attrs/attrs.js +7 -4
  178. package/src/directive/attrs/attrs.spec.js +1 -1
  179. package/src/directive/attrs/attrs.test.js +1 -0
  180. package/src/directive/attrs/boolean.spec.js +1 -1
  181. package/src/directive/attrs/boolean.test.js +1 -0
  182. package/src/directive/attrs/element-style.spec.js +1 -1
  183. package/src/directive/attrs/element-style.test.js +1 -0
  184. package/src/directive/attrs/src.spec.js +1 -1
  185. package/src/directive/attrs/src.test.js +1 -0
  186. package/src/directive/bind/bind-html.spec.js +1 -1
  187. package/src/directive/bind/bind.js +1 -0
  188. package/src/directive/bind/bind.spec.js +1 -1
  189. package/src/directive/bind/bind.test.js +1 -0
  190. package/src/directive/channel/channel.spec.js +1 -1
  191. package/src/directive/channel/channel.test.js +1 -0
  192. package/src/directive/class/class.spec.js +1 -1
  193. package/src/directive/class/class.test.js +1 -0
  194. package/src/directive/cloak/cloak.spec.js +1 -1
  195. package/src/directive/cloak/cloak.test.js +1 -0
  196. package/src/directive/controller/controller.spec.js +1 -1
  197. package/src/directive/controller/controller.test.js +1 -0
  198. package/src/directive/events/click.spec.js +1 -1
  199. package/src/directive/events/event.spec.js +1 -1
  200. package/src/directive/events/events.js +6 -2
  201. package/src/directive/events/events.test.js +1 -0
  202. package/src/directive/form/form.js +8 -5
  203. package/src/directive/form/form.spec.js +1 -1
  204. package/src/directive/form/form.test.js +1 -0
  205. package/src/directive/http/delete.spec.js +3 -1
  206. package/src/directive/http/form-test.html +18 -0
  207. package/src/directive/http/get.spec.js +281 -4
  208. package/src/directive/http/http.js +112 -15
  209. package/src/directive/http/http.test.js +2 -2
  210. package/src/directive/http/post.spec.js +506 -9
  211. package/src/directive/http/put.spec.js +3 -1
  212. package/src/directive/if/if.spec.js +1 -1
  213. package/src/directive/include/include.js +7 -7
  214. package/src/directive/include/include.spec.js +1 -1
  215. package/src/directive/init/init.spec.js +1 -1
  216. package/src/directive/init/init.test.js +1 -0
  217. package/src/directive/input/input.js +19 -43
  218. package/src/directive/input/input.spec.js +1 -2
  219. package/src/directive/input/input.test.js +1 -0
  220. package/src/directive/messages/messages.js +4 -0
  221. package/src/directive/messages/messages.spec.js +1 -1
  222. package/src/directive/messages/messages.test.js +1 -0
  223. package/src/directive/model/model.js +14 -14
  224. package/src/directive/model/model.spec.js +1 -1
  225. package/src/directive/model/model.test.js +1 -0
  226. package/src/directive/model-options/model-option.test.js +1 -0
  227. package/src/directive/model-options/model-options.js +1 -1
  228. package/src/directive/model-options/model-options.spec.js +1 -1
  229. package/src/directive/non-bindable/non-bindable.spec.js +1 -1
  230. package/src/directive/non-bindable/non-bindable.test.js +1 -0
  231. package/src/directive/observe/observe.spec.js +1 -1
  232. package/src/directive/observe/observe.test.js +1 -0
  233. package/src/directive/on/on.spec.js +1 -1
  234. package/src/directive/on/on.test.js +1 -0
  235. package/src/directive/options/options.js +454 -464
  236. package/src/directive/options/options.spec.js +1 -1
  237. package/src/directive/options/options.test.js +1 -0
  238. package/src/directive/ref/href.spec.js +1 -1
  239. package/src/directive/ref/href.test.js +2 -0
  240. package/src/directive/ref/ref.spec.js +1 -1
  241. package/src/directive/repeat/repeat.spec.js +2 -3
  242. package/src/directive/repeat/repeat.test.js +1 -0
  243. package/src/directive/script/script.spec.js +1 -1
  244. package/src/directive/script/script.test.js +1 -0
  245. package/src/directive/select/select.js +1 -1
  246. package/src/directive/select/select.spec.js +1 -1
  247. package/src/directive/select/select.test.js +1 -0
  248. package/src/directive/setter/setter.js +12 -14
  249. package/src/directive/setter/setter.spec.js +40 -17
  250. package/src/directive/setter/setter.test.js +1 -0
  251. package/src/directive/show-hide/show-hide.spec.js +1 -1
  252. package/src/directive/show-hide/show-hide.test.js +1 -0
  253. package/src/directive/style/style.spec.js +1 -1
  254. package/src/directive/style/style.test.js +1 -0
  255. package/src/directive/switch/switch.js +1 -0
  256. package/src/directive/switch/switch.spec.js +1 -1
  257. package/src/directive/switch/switch.test.js +1 -0
  258. package/src/directive/transclude/transclude.js +87 -89
  259. package/src/directive/validators/validators.js +82 -84
  260. package/src/directive/validators/validators.spec.js +5 -4
  261. package/src/directive/validators/validators.test.js +1 -0
  262. package/src/filters/filter.spec.js +1 -1
  263. package/src/filters/filters.spec.js +1 -1
  264. package/src/filters/limit-to.js +2 -3
  265. package/src/filters/limit-to.spec.js +1 -1
  266. package/src/filters/order-by.spec.js +1 -1
  267. package/src/index.js +1 -1
  268. package/src/injection-tokens.js +6 -2
  269. package/src/interface.ts +70 -19
  270. package/src/loader.md +0 -155
  271. package/src/{public.js → ng.js} +16 -23
  272. package/src/{public.spec.js → ng.spec.js} +1 -1
  273. package/src/router/directives/state-directives.spec.js +9 -8
  274. package/src/router/directives/view-directive.js +11 -9
  275. package/src/router/directives/view-directive.spec.js +8 -9
  276. package/src/router/{common/common.html → glob/glob.html} +2 -3
  277. package/src/router/{common → glob}/glob.js +5 -0
  278. package/src/router/{common/common.test.js → glob/glob.test.js} +2 -1
  279. package/src/router/globals.js +1 -2
  280. package/src/router/path/path-utils.js +5 -0
  281. package/src/router/router-test-hashbang.html +45 -0
  282. package/src/router/services.spec.js +5 -6
  283. package/src/router/state/interface.ts +1 -1
  284. package/src/router/state/state-builder.js +3 -3
  285. package/src/router/state/state-builder.spec.js +1 -1
  286. package/src/router/state/state-object.js +1 -1
  287. package/src/router/state/state-registry.js +2 -3
  288. package/src/router/state/state-service.js +13 -10
  289. package/src/router/state/state.spec.js +23 -22
  290. package/src/router/state/state.test.js +1 -0
  291. package/src/router/state/views.js +1 -1
  292. package/src/router/state-filter.spec.js +1 -1
  293. package/src/router/state-filters.js +15 -11
  294. package/src/router/template-factory.js +5 -4
  295. package/src/router/template-factory.spec.js +1 -1
  296. package/src/router/transition/hook-registry.js +1 -1
  297. package/src/router/transition/transition-service.js +6 -5
  298. package/src/router/transition/transition.js +4 -4
  299. package/src/router/url/url-matcher.js +3 -3
  300. package/src/router/url/url-rule.js +1 -0
  301. package/src/router/url/url-rules.js +8 -5
  302. package/src/router/url/url-service.js +82 -85
  303. package/src/router/url/url-service.spec.js +55 -39
  304. package/src/router/url/url.test.js +1 -0
  305. package/src/router/view/view.js +4 -5
  306. package/src/router/view/view.spec.js +10 -12
  307. package/src/router/view/view.test.js +1 -0
  308. package/src/router/view-hook.spec.js +1 -1
  309. package/src/router/view-scroll.spec.js +1 -1
  310. package/src/services/anchor-scroll.html +2 -9
  311. package/src/services/anchor-scroll.js +6 -5
  312. package/src/{core → services/exception}/exception-handler.js +2 -2
  313. package/src/{core/error-handler.ts → services/exception/interface.ts} +1 -1
  314. package/src/services/http/http.js +11 -17
  315. package/src/services/http/http.spec.js +2 -7
  316. package/src/services/http/interface.ts +2 -2
  317. package/src/services/http/template-request.spec.js +1 -1
  318. package/src/services/http-backend/http-backend.js +53 -89
  319. package/src/services/http-backend/http-backend.spec.js +2 -6
  320. package/src/services/http-backend/http-backend.test.js +1 -0
  321. package/src/services/location/interface.ts +70 -0
  322. package/src/{core → services}/location/location.html +4 -1
  323. package/src/services/location/location.js +999 -0
  324. package/src/{core → services}/location/location.spec.js +911 -532
  325. package/src/{core → services}/location/location.test.js +2 -2
  326. package/src/services/log/log.spec.js +1 -1
  327. package/src/services/log/log.test.js +1 -0
  328. package/src/services/pubsub/pubsub.spec.js +1 -1
  329. package/src/{core → services}/sce/sce.html +1 -1
  330. package/src/{core → services}/sce/sce.js +14 -10
  331. package/src/{core → services}/sce/sce.md +2 -2
  332. package/src/{core → services}/sce/sce.spec.js +3 -4
  333. package/src/{core → services}/sce/sce.test.js +1 -1
  334. package/src/services/template-cache/interface.ts +8 -2
  335. package/src/services/template-cache/template-cache.js +3 -1
  336. package/src/services/template-cache/template-cache.spec.js +73 -1
  337. package/src/services/template-cache/template-cache.test.js +1 -0
  338. package/src/services/template-request.js +2 -1
  339. package/src/shared/cache.js +0 -2
  340. package/src/shared/common.js +0 -5
  341. package/src/shared/common.spec.js +1 -1
  342. package/src/shared/dom.js +10 -0
  343. package/src/shared/interface.ts +0 -4
  344. package/src/{router/common → shared}/queue.js +7 -7
  345. package/src/shared/shared.html +1 -0
  346. package/src/shared/shared.test.js +1 -0
  347. package/src/shared/test-utils.js +1 -0
  348. package/src/shared/url-utils/interface.ts +54 -0
  349. package/src/{core → shared}/url-utils/url-utils.html +4 -1
  350. package/src/{core → shared}/url-utils/url-utils.js +26 -47
  351. package/src/{core → shared}/url-utils/url-utils.spec.js +10 -17
  352. package/src/{core → shared}/url-utils/url-utils.test.js +1 -1
  353. package/src/shared/utils.js +60 -9
  354. package/src/shared/utils.spec.js +35 -1
  355. package/src/src.html +1 -2
  356. package/typedoc.json +0 -1
  357. package/utils/express.js +36 -2
  358. package/utils/version.cjs +23 -0
  359. package/@types/core/location/location.d.ts +0 -317
  360. package/@types/core/task-tracker-factory.d.ts +0 -76
  361. package/@types/core/url-utils/url-utils.d.ts +0 -56
  362. package/@types/router/state-provider.d.ts +0 -123
  363. package/@types/services/browser.d.ts +0 -101
  364. package/docs/static/typedoc/types/SwapInsertPosition.html +0 -2
  365. package/jsdoc.json +0 -22
  366. package/src/core/location/location.js +0 -984
  367. package/src/core/location/location.md +0 -114
  368. package/src/core/task-tracker-factory.js +0 -145
  369. package/src/core/url-utils/url-utils.md +0 -46
  370. package/src/directive/non-bindable/non-bindable.md +0 -17
  371. package/src/loader.spec.js +0 -169
  372. package/src/router/state-provider.js +0 -146
  373. package/src/services/browser.js +0 -212
  374. /package/@types/{loader.d.ts → angular.d.ts} +0 -0
  375. /package/src/router/{common → glob}/glob.spec.js +0 -0
  376. /package/src/{router/common → shared}/queue.spec.js +0 -0
@@ -0,0 +1,999 @@
1
+ import { trimEmptyHash, urlResolve } from "../../shared/url-utils/url-utils.js";
2
+ import {
3
+ encodeUriSegment,
4
+ isDefined,
5
+ isNumber,
6
+ isObject,
7
+ isString,
8
+ isUndefined,
9
+ minErr,
10
+ parseKeyValue,
11
+ toKeyValue,
12
+ equals,
13
+ startsWith,
14
+ } from "../../shared/utils.js";
15
+ import { getBaseHref } from "../../shared/dom.js";
16
+ import { $injectTokens as $t } from "../../injection-tokens.js";
17
+
18
+ const PATH_MATCH = /^([^?#]*)(\?([^#]*))?(#(.*))?$/;
19
+ const $locationMinErr = minErr("$location");
20
+
21
+ let urlUpdatedByLocation = false;
22
+
23
+ /**
24
+ * @ignore
25
+ * The pathname, beginning with "/"
26
+ * @type {string}
27
+ */
28
+ let $$path;
29
+
30
+ /**
31
+ * @type {Object.<string,boolean|Array>}
32
+ */
33
+ let $$search;
34
+
35
+ /**
36
+ * @ignore
37
+ * The hash string, minus the hash symbol
38
+ * @type {string}
39
+ */
40
+ let $$hash;
41
+
42
+ export class Location {
43
+ /**
44
+ * @param {string} appBase application base URL
45
+ * @param {string} appBaseNoFile application base URL stripped of any filename
46
+ * @param {boolean} [html5] Defaults to true
47
+ * @param {string} [prefix] URL path prefix for html5 mode or hash prefix for hashbang mode
48
+ */
49
+ constructor(appBase, appBaseNoFile, html5 = true, prefix) {
50
+ /** @type {string} */
51
+ this.appBase = appBase;
52
+
53
+ /** @type {string} */
54
+ this.appBaseNoFile = appBaseNoFile;
55
+
56
+ /** @type {boolean} */
57
+ this.html5 = html5;
58
+
59
+ /** @type {string | undefined} */
60
+ this.basePrefix = html5 ? prefix || "" : undefined;
61
+
62
+ /** @type {string | undefined} */
63
+ this.hashPrefix = html5 ? undefined : prefix;
64
+
65
+ /**
66
+ * An absolute URL is the full URL, including protocol (http/https ), the optional subdomain (e.g. www ), domain (example.com), and path (which includes the directory and slug)
67
+ * with all segments encoded according to rules specified in [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
68
+ * @type {string}
69
+ */
70
+ this.absUrl = "";
71
+
72
+ /**
73
+ * @ignore
74
+ * Current url
75
+ * @type {string}
76
+ */
77
+ this.$$url = undefined;
78
+
79
+ /**
80
+ * @ignore
81
+ * Callback to update browser url
82
+ * @type {Function}
83
+ */
84
+ this.$$updateBrowser = undefined;
85
+ }
86
+
87
+ /**
88
+ * Change path, search and hash, when called with parameter and return `$location`.
89
+ *
90
+ * @param {string} url New URL without base prefix (e.g. `/path?a=b#hash`)
91
+ * @return {Location} url
92
+ */
93
+ setUrl(url) {
94
+ const match = PATH_MATCH.exec(url);
95
+ if (match[1] || url === "") this.setPath(decodeURIComponent(match[1]));
96
+ if (match[2] || match[1] || url === "") this.setSearch(match[3] || "");
97
+ this.setHash(match[5] || "");
98
+
99
+ return this;
100
+ }
101
+
102
+ /**
103
+ * Return URL (e.g. `/path?a=b#hash`) when called without any parameter.
104
+ *
105
+ * @return {string} url
106
+ */
107
+ getUrl() {
108
+ return this.$$url;
109
+ }
110
+
111
+ /**
112
+ * Change path parameter and return `$location`.
113
+ *
114
+ * @param {(string|number)} path New path
115
+ * @return {Location}
116
+ */
117
+ setPath(path) {
118
+ let newPath = path !== null ? path.toString() : "";
119
+ $$path = newPath.charAt(0) === "/" ? newPath : `/${newPath}`;
120
+ this.$$compose();
121
+ return this;
122
+ }
123
+
124
+ /**
125
+ *
126
+ * Return path of current URL
127
+ *
128
+ * @return {string}
129
+ */
130
+ getPath() {
131
+ return $$path;
132
+ }
133
+
134
+ /**
135
+ * Changes the hash fragment when called with a parameter and returns `$location`.
136
+ * @param {(string|number)} hash New hash fragment
137
+ * @return {Location} hash
138
+ */
139
+ setHash(hash) {
140
+ $$hash = hash !== null ? hash.toString() : "";
141
+ this.$$compose();
142
+ return this;
143
+ }
144
+
145
+ /**
146
+ * Returns the hash fragment when called without any parameters.
147
+ * @return {string} hash
148
+ */
149
+ getHash() {
150
+ return $$hash;
151
+ }
152
+
153
+ /**
154
+ * Sets the search part (as object) of current URL
155
+ *
156
+ * @param {string|Object} search New search params - string or hash object.
157
+ * @param {(string|number|Array<string>|boolean)=} paramValue If search is a string or number, then paramValue will override only a single search property.
158
+ * @returns {Object} Search object or Location object
159
+ */
160
+ setSearch(search, paramValue) {
161
+ switch (arguments.length) {
162
+ case 1:
163
+ if (isString(search) || isNumber(search)) {
164
+ search = search.toString();
165
+ $$search = parseKeyValue(search);
166
+ } else if (isObject(search)) {
167
+ search = structuredClone(search, {});
168
+ // remove object undefined or null properties
169
+ Object.entries(search).forEach(([key, value]) => {
170
+ if (value == null) delete search[key];
171
+ });
172
+
173
+ $$search = search;
174
+ } else {
175
+ throw $locationMinErr(
176
+ "isrcharg",
177
+ "The first argument of the `$location#search()` call must be a string or an object.",
178
+ );
179
+ }
180
+ break;
181
+ default:
182
+ if (isUndefined(paramValue) || paramValue === null) {
183
+ delete $$search[search];
184
+ } else {
185
+ // @ts-ignore
186
+ $$search[search] = paramValue;
187
+ }
188
+ }
189
+
190
+ this.$$compose();
191
+ return this;
192
+ }
193
+
194
+ /**
195
+ * Returns the search part (as object) of current URL
196
+ *
197
+ * @returns {Object} Search object or Location object
198
+ */
199
+ getSearch() {
200
+ return $$search;
201
+ }
202
+
203
+ /**
204
+ * @private
205
+ * Compose url and update `url` and `absUrl` property
206
+ */
207
+ $$compose() {
208
+ this.$$url = normalizePath($$path, $$search, $$hash);
209
+ this.absUrl = this.html5
210
+ ? this.appBaseNoFile + this.$$url.substring(1)
211
+ : this.appBase + (this.$$url ? this.hashPrefix + this.$$url : "");
212
+ urlUpdatedByLocation = true;
213
+ setTimeout(() => this.$$updateBrowser && this.$$updateBrowser());
214
+ }
215
+
216
+ /**
217
+ * Change the history state object when called with one parameter and return `$location`.
218
+ * The state object is later passed to `pushState` or `replaceState`.
219
+ * See {@link https://developer.mozilla.org/en-US/docs/Web/API/History/pushState#state|History.state}
220
+ *
221
+ * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
222
+ * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
223
+ * older browsers (like IE9 or Android < 4.0), don't use this method.
224
+ * @param {any} state
225
+ * @returns {Location}
226
+ */
227
+ setState(state) {
228
+ if (!this.html5) {
229
+ throw $locationMinErr(
230
+ "nostate",
231
+ "History API state support is available only in HTML5 mode",
232
+ );
233
+ }
234
+ // The user might modify `stateObject` after invoking `$location.setState(stateObject)`
235
+ // but we're changing the $$state reference to $browser.state() during the $digest
236
+ // so the modification window is narrow.
237
+ this.$$state = isUndefined(state) ? null : state;
238
+ urlUpdatedByLocation = true;
239
+ return this;
240
+ }
241
+
242
+ /**
243
+ * Return the history state object
244
+ * @returns {any}
245
+ */
246
+ getState() {
247
+ return this.$$state;
248
+ }
249
+
250
+ /**
251
+ * @param {string} url
252
+ * @param {string} relHref
253
+ * @returns {boolean}
254
+ */
255
+ parseLinkUrl(url, relHref) {
256
+ if (this.html5) {
257
+ if (relHref && relHref[0] === "#") {
258
+ // special case for links to hash fragments:
259
+ // keep the old url and only replace the hash fragment
260
+ this.setHash(relHref.slice(1));
261
+ return true;
262
+ }
263
+ let appUrl;
264
+ let prevAppUrl;
265
+ let rewrittenUrl;
266
+
267
+ if (isDefined((appUrl = stripBaseUrl(this.appBase, url)))) {
268
+ prevAppUrl = appUrl;
269
+ if (
270
+ this.basePrefix &&
271
+ isDefined((appUrl = stripBaseUrl(this.basePrefix, appUrl)))
272
+ ) {
273
+ rewrittenUrl =
274
+ this.appBaseNoFile + (stripBaseUrl("/", appUrl) || appUrl);
275
+ } else {
276
+ rewrittenUrl = this.appBase + prevAppUrl;
277
+ }
278
+ } else if (isDefined((appUrl = stripBaseUrl(this.appBaseNoFile, url)))) {
279
+ rewrittenUrl = this.appBaseNoFile + appUrl;
280
+ } else if (this.appBaseNoFile === `${url}/`) {
281
+ rewrittenUrl = this.appBaseNoFile;
282
+ }
283
+ if (rewrittenUrl) {
284
+ this.parse(rewrittenUrl);
285
+ }
286
+ return !!rewrittenUrl;
287
+ } else {
288
+ if (stripHash(this.appBase) === stripHash(url)) {
289
+ this.parse(url);
290
+ return true;
291
+ }
292
+ return false;
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Parse given HTML5 (regular) URL string into properties
298
+ * @param {string} url HTML5 URL
299
+ */
300
+ parse(url) {
301
+ if (this.html5) {
302
+ const pathUrl = stripBaseUrl(this.appBaseNoFile, url);
303
+ if (!isString(pathUrl)) {
304
+ throw $locationMinErr(
305
+ "ipthprfx",
306
+ 'Invalid url "{0}", missing path prefix "{1}".',
307
+ url,
308
+ this.appBaseNoFile,
309
+ );
310
+ }
311
+
312
+ parseAppUrl(pathUrl, true);
313
+
314
+ if (!$$path) {
315
+ $$path = "/";
316
+ }
317
+
318
+ this.$$compose();
319
+ } else {
320
+ const withoutBaseUrl =
321
+ stripBaseUrl(this.appBase, url) ||
322
+ stripBaseUrl(this.appBaseNoFile, url);
323
+ let withoutHashUrl;
324
+
325
+ if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === "#") {
326
+ // The rest of the URL starts with a hash so we have
327
+ // got either a hashbang path or a plain hash fragment
328
+ withoutHashUrl = stripBaseUrl(this.hashPrefix, withoutBaseUrl);
329
+ if (isUndefined(withoutHashUrl)) {
330
+ // There was no hashbang prefix so we just have a hash fragment
331
+ withoutHashUrl = withoutBaseUrl;
332
+ }
333
+ } else {
334
+ // There was no hashbang path nor hash fragment:
335
+ // If we are in HTML5 mode we use what is left as the path;
336
+ // Otherwise we ignore what is left
337
+ if (this.html5) {
338
+ withoutHashUrl = withoutBaseUrl;
339
+ } else {
340
+ withoutHashUrl = "";
341
+ if (isUndefined(withoutBaseUrl)) {
342
+ this.appBase = url;
343
+ }
344
+ }
345
+ }
346
+
347
+ parseAppUrl(withoutHashUrl, false);
348
+
349
+ $$path = removeWindowsDriveName($$path, withoutHashUrl, this.appBase);
350
+
351
+ this.$$compose();
352
+
353
+ /*
354
+ * In Windows, on an anchor node on documents loaded from
355
+ * the filesystem, the browser will return a pathname
356
+ * prefixed with the drive name ('/C:/path') when a
357
+ * pathname without a drive is set:
358
+ * * a.setAttribute('href', '/foo')
359
+ * * a.pathname === '/C:/foo' //true
360
+ *
361
+ * Inside of AngularTS, we're always using pathnames that
362
+ * do not include drive names for routing.
363
+ */
364
+ function removeWindowsDriveName(path, url, base) {
365
+ /*
366
+ Matches paths for file protocol on windows,
367
+ such as /C:/foo/bar, and captures only /foo/bar.
368
+ */
369
+ const windowsFilePathExp = /^\/[A-Z]:(\/.*)/;
370
+
371
+ let firstPathSegmentMatch;
372
+
373
+ // Get the relative path from the input URL.
374
+ if (startsWith(url, base)) {
375
+ url = url.replace(base, "");
376
+ }
377
+
378
+ // The input URL intentionally contains a first path segment that ends with a colon.
379
+ if (windowsFilePathExp.exec(url)) {
380
+ return path;
381
+ }
382
+
383
+ firstPathSegmentMatch = windowsFilePathExp.exec(path);
384
+ return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path;
385
+ }
386
+ }
387
+ }
388
+ }
389
+
390
+ export class LocationProvider {
391
+ constructor() {
392
+ /** @type {string} */
393
+ this.hashPrefixConf = "!";
394
+
395
+ /** @type {import("./interface.ts").Html5Mode} */
396
+ this.html5ModeConf = {
397
+ enabled: true,
398
+ requireBase: false,
399
+ rewriteLinks: true,
400
+ };
401
+
402
+ /** @type {Array<import("./interface.ts").UrlChangeListener>} */
403
+ this.urlChangeListeners = [];
404
+ this.urlChangeInit = false;
405
+
406
+ /** @type {History['state']} */
407
+ this.cachedState = null;
408
+ /** @type {History['state']} */
409
+ this.lastHistoryState = null;
410
+ /** @type {string} */
411
+ this.lastBrowserUrl = window.location.href;
412
+ this.cacheState();
413
+ }
414
+
415
+ /// ///////////////////////////////////////////////////////////
416
+ // URL API
417
+ /// ///////////////////////////////////////////////////////////
418
+
419
+ /**
420
+ * Updates the browser's current URL and history state.
421
+ *
422
+ * @param {string|undefined} url - The target URL to navigate to.
423
+ * @param {*} [state=null] - Optional history state object to associate with the new URL.
424
+ * @returns {LocationProvider}
425
+ */
426
+ setUrl(url, state) {
427
+ if (state === undefined) {
428
+ state = null;
429
+ }
430
+ if (url) {
431
+ url = new URL(url).href;
432
+
433
+ if (this.lastBrowserUrl === url && this.lastHistoryState === state) {
434
+ return this;
435
+ }
436
+
437
+ this.lastBrowserUrl = url;
438
+ this.lastHistoryState = state;
439
+ history.pushState(state, "", url);
440
+ this.cacheState();
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Returns the current URL with any empty hash (`#`) removed.
446
+ * @return {string}
447
+ */
448
+ getBrowserUrl() {
449
+ return trimEmptyHash(window.location.href);
450
+ }
451
+
452
+ /**
453
+ * Returns the cached state.
454
+ * @returns {History['state']} The cached state.
455
+ */
456
+ state() {
457
+ return this.cachedState;
458
+ }
459
+
460
+ /**
461
+ * Caches the current state.
462
+ *
463
+ * @private
464
+ */
465
+ cacheState() {
466
+ const currentState = history.state ?? null;
467
+ if (!equals(currentState, this.lastCachedState)) {
468
+ this.cachedState = currentState;
469
+ this.lastCachedState = currentState;
470
+ this.lastHistoryState = currentState;
471
+ }
472
+ }
473
+
474
+ /**
475
+ * Fires the state or URL change event.
476
+ */
477
+ #fireStateOrUrlChange() {
478
+ const prevLastHistoryState = this.lastHistoryState;
479
+ this.cacheState();
480
+ if (
481
+ this.lastBrowserUrl === this.getBrowserUrl() &&
482
+ prevLastHistoryState === this.cachedState
483
+ ) {
484
+ return;
485
+ }
486
+ this.lastBrowserUrl = this.getBrowserUrl();
487
+ this.lastHistoryState = this.cachedState;
488
+ this.urlChangeListeners.forEach((listener) => {
489
+ listener(trimEmptyHash(window.location.href), this.cachedState);
490
+ });
491
+ }
492
+
493
+ /**
494
+ * Registers a callback to be called when the URL changes.
495
+ *
496
+ * @param {import("./interface.js").UrlChangeListener} callback - The callback function to register.
497
+ * @returns void
498
+ */
499
+ #onUrlChange(callback) {
500
+ if (!this.urlChangeInit) {
501
+ window.addEventListener(
502
+ "popstate",
503
+ this.#fireStateOrUrlChange.bind(this),
504
+ );
505
+ window.addEventListener(
506
+ "hashchange",
507
+ this.#fireStateOrUrlChange.bind(this),
508
+ );
509
+ this.urlChangeInit = true;
510
+ }
511
+ this.urlChangeListeners.push(callback);
512
+ }
513
+
514
+ $get = [
515
+ $t.$rootScope,
516
+ $t.$rootElement,
517
+ /**
518
+ *
519
+ * @param {import('../../core/scope/scope.js').Scope} $rootScope
520
+ * @param {Element} $rootElement
521
+ * @returns {Location}
522
+ */
523
+ ($rootScope, $rootElement) => {
524
+ /** @type {Location} */
525
+ let $location;
526
+ const baseHref = getBaseHref(); // if base[href] is undefined, it defaults to ''
527
+ const initialUrl = trimEmptyHash(window.location.href);
528
+ let appBase;
529
+
530
+ if (this.html5ModeConf.enabled) {
531
+ if (!baseHref && this.html5ModeConf.requireBase) {
532
+ throw $locationMinErr(
533
+ "nobase",
534
+ "$location in HTML5 mode requires a <base> tag to be present!",
535
+ );
536
+ }
537
+ appBase = serverBase(initialUrl) + (baseHref || "/");
538
+ } else {
539
+ appBase = stripHash(initialUrl);
540
+ }
541
+ const appBaseNoFile = stripFile(appBase);
542
+
543
+ $location = new Location(
544
+ appBase,
545
+ appBaseNoFile,
546
+ this.html5ModeConf.enabled,
547
+ `#${this.hashPrefixConf}`,
548
+ );
549
+ $location.parseLinkUrl(initialUrl, initialUrl);
550
+
551
+ $location.$$state = this.state();
552
+
553
+ const IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i;
554
+
555
+ const setBrowserUrlWithFallback = (url, state) => {
556
+ const oldUrl = $location.getUrl();
557
+ const oldState = $location.$$state;
558
+ try {
559
+ this.setUrl(url, state);
560
+
561
+ // Make sure $location.getState() returns referentially identical (not just deeply equal)
562
+ // state object; this makes possible quick checking if the state changed in the digest
563
+ // loop. Checking deep equality would be too expensive.
564
+ $location.$$state = this.state();
565
+ } catch (e) {
566
+ // Restore old values if pushState fails
567
+ $location.setUrl(/** @type {string} */ (oldUrl));
568
+ $location.$$state = oldState;
569
+
570
+ throw e;
571
+ }
572
+ };
573
+
574
+ $rootElement.addEventListener(
575
+ "click",
576
+ /** @param {MouseEvent} event */
577
+ (event) => {
578
+ const rewriteLinks = this.html5ModeConf.rewriteLinks;
579
+ // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
580
+ // currently we open nice url link and redirect then
581
+
582
+ if (
583
+ !rewriteLinks ||
584
+ event.ctrlKey ||
585
+ event.metaKey ||
586
+ event.shiftKey ||
587
+ event.button === 2
588
+ ) {
589
+ return;
590
+ }
591
+ let elm = /** @type {HTMLAnchorElement} */ (event.target);
592
+
593
+ // traverse the DOM up to find first A tag
594
+ while (elm.nodeName.toLowerCase() !== "a") {
595
+ // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
596
+ // @ts-ignore
597
+ if (elm === $rootElement || !(elm = elm.parentElement)) return;
598
+ }
599
+
600
+ if (
601
+ isString(rewriteLinks) &&
602
+ isUndefined(elm.getAttribute(/** @type {string} */ (rewriteLinks)))
603
+ ) {
604
+ return;
605
+ }
606
+
607
+ let absHref = elm.href;
608
+ // get the actual href attribute - see
609
+ // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx
610
+ const relHref =
611
+ elm.getAttribute("href") || elm.getAttribute("xlink:href");
612
+
613
+ if (
614
+ isObject(absHref) &&
615
+ absHref.toString() === "[object SVGAnimatedString]"
616
+ ) {
617
+ // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during
618
+ // an animation.
619
+
620
+ const scvAnimatedString = /** @type {unknown} */ (absHref);
621
+ absHref = new URL(
622
+ /** @type {SVGAnimatedString } */ (scvAnimatedString).animVal,
623
+ ).href;
624
+ }
625
+
626
+ // Ignore when url is started with javascript: or mailto:
627
+ if (IGNORE_URI_REGEXP.test(absHref)) return;
628
+
629
+ if (
630
+ absHref &&
631
+ !elm.getAttribute("target") &&
632
+ !event.defaultPrevented
633
+ ) {
634
+ if ($location.parseLinkUrl(absHref, relHref)) {
635
+ // We do a preventDefault for all urls that are part of the AngularTS application,
636
+ // in html5mode and also without, so that we are able to abort navigation without
637
+ // getting double entries in the location history.
638
+ event.preventDefault();
639
+ }
640
+ }
641
+ },
642
+ );
643
+
644
+ // rewrite hashbang url <> html5 url
645
+ if ($location.absUrl !== initialUrl) {
646
+ this.setUrl($location.absUrl, true);
647
+ }
648
+
649
+ let initializing = true;
650
+
651
+ // update $location when $browser url changes
652
+ this.#onUrlChange((newUrl, newState) => {
653
+ if (!startsWith(newUrl, appBaseNoFile)) {
654
+ // If we are navigating outside of the app then force a reload
655
+ window.location.href = newUrl;
656
+ return;
657
+ }
658
+
659
+ Promise.resolve().then(() => {
660
+ const oldUrl = $location.absUrl;
661
+ const oldState = $location.$$state;
662
+ let defaultPrevented;
663
+ $location.parse(newUrl);
664
+ $location.$$state = newState;
665
+
666
+ defaultPrevented = $rootScope.$broadcast(
667
+ "$locationChangeStart",
668
+ newUrl,
669
+ oldUrl,
670
+ newState,
671
+ oldState,
672
+ ).defaultPrevented;
673
+
674
+ // if the location was changed by a `$locationChangeStart` handler then stop
675
+ // processing this location change
676
+ if ($location.absUrl !== newUrl) return;
677
+
678
+ if (defaultPrevented) {
679
+ $location.parse(oldUrl);
680
+ $location.$$state = oldState;
681
+ setBrowserUrlWithFallback(oldUrl, oldState);
682
+ } else {
683
+ initializing = false;
684
+ afterLocationChange(oldUrl, oldState);
685
+ }
686
+ });
687
+ });
688
+
689
+ // update browser
690
+ const updateBrowser = () => {
691
+ if (initializing || urlUpdatedByLocation) {
692
+ urlUpdatedByLocation = false;
693
+
694
+ const oldUrl = /** @type {string} */ (this.getBrowserUrl());
695
+ const newUrl = $location.absUrl;
696
+ const oldState = this.state();
697
+ const urlOrStateChanged =
698
+ !urlsEqual(oldUrl, newUrl) ||
699
+ ($location.html5 && oldState !== $location.$$state);
700
+
701
+ if (initializing || urlOrStateChanged) {
702
+ initializing = false;
703
+
704
+ setTimeout(() => {
705
+ const newUrl = $location.absUrl;
706
+ const { defaultPrevented } = $rootScope.$broadcast(
707
+ "$locationChangeStart",
708
+ newUrl,
709
+ oldUrl,
710
+ $location.$$state,
711
+ oldState,
712
+ );
713
+
714
+ // if the location was changed by a `$locationChangeStart` handler then stop
715
+ // processing this location change
716
+ if ($location.absUrl !== newUrl) return;
717
+
718
+ if (defaultPrevented) {
719
+ $location.parse(oldUrl);
720
+ $location.$$state = oldState;
721
+ } else {
722
+ if (urlOrStateChanged) {
723
+ setBrowserUrlWithFallback(
724
+ newUrl,
725
+ oldState === $location.$$state ? null : $location.$$state,
726
+ );
727
+ }
728
+ afterLocationChange(oldUrl, oldState);
729
+ }
730
+ });
731
+ }
732
+ }
733
+ };
734
+ $location.$$updateBrowser = updateBrowser;
735
+ updateBrowser();
736
+ $rootScope.$on("$updateBrowser", updateBrowser);
737
+
738
+ return $location;
739
+
740
+ function afterLocationChange(oldUrl, oldState) {
741
+ $rootScope.$broadcast(
742
+ "$locationChangeSuccess",
743
+ $location.absUrl,
744
+ oldUrl,
745
+ $location.$$state,
746
+ oldState,
747
+ );
748
+ }
749
+ },
750
+ ];
751
+ }
752
+
753
+ /**
754
+ * ///////////////////////////
755
+ * PRIVATE HELPERS
756
+ * ///////////////////////////
757
+ */
758
+
759
+ /**
760
+ * @ignore
761
+ * Encodes a URL path by encoding each path segment individually using `encodeUriSegment`,
762
+ * while preserving forward slashes (`/`) as segment separators.
763
+ *
764
+ * This function first decodes any existing percent-encodings (such as `%20` or `%2F`)
765
+ * in each segment to prevent double encoding, except for encoded forward slashes (`%2F`),
766
+ * which are replaced with literal slashes before decoding to keep path boundaries intact.
767
+ *
768
+ * After decoding, each segment is re-encoded with `encodeUriSegment` according to RFC 3986,
769
+ * encoding only characters that must be encoded in a path segment.
770
+ *
771
+ * The encoded segments are then rejoined with `/` to form the encoded path.
772
+ *
773
+ * @param {string} path - The URL path string to encode. May contain multiple segments separated by `/`.
774
+ * @returns {string} The encoded path, where each segment is encoded, but forward slashes are preserved.
775
+ *
776
+ * @example
777
+ * encodePath("user profile/images/pic 1.jpg")
778
+ * // returns "user%20profile/images/pic%201.jpg"
779
+ *
780
+ * @example
781
+ * encodePath("folder1%2Fsub/folder2")
782
+ * // returns "folder1%2Fsub/folder2"
783
+ */
784
+ export function encodePath(path) {
785
+ const segments = path.split("/");
786
+ let i = segments.length;
787
+
788
+ while (i--) {
789
+ // Decode any existing encodings (e.g. %20, %2F) to prevent double-encoding
790
+ // But keep slashes intact (they were split on)
791
+ const decodedSegment = decodeURIComponent(
792
+ segments[i].replace(/%2F/gi, "/"),
793
+ );
794
+ segments[i] = encodeUriSegment(decodedSegment);
795
+ }
796
+
797
+ return segments.join("/");
798
+ }
799
+
800
+ /**
801
+ * @ignore
802
+ * Decodes each segment of a URL path.
803
+ *
804
+ * Splits the input path by "/", decodes each segment using decodeURIComponent,
805
+ * and if html5Mode is enabled, re-encodes any forward slashes inside segments
806
+ * as "%2F" to avoid confusion with path separators.
807
+ *
808
+ * @param {string} path - The URL path to decode.
809
+ * @param {boolean} html5Mode - If true, encodes forward slashes in segments as "%2F".
810
+ * @returns {string} The decoded path with segments optionally encoding slashes.
811
+ */
812
+ export function decodePath(path, html5Mode) {
813
+ const segments = path.split("/");
814
+ let i = segments.length;
815
+
816
+ while (i--) {
817
+ segments[i] = decodeURIComponent(segments[i]);
818
+ if (html5Mode) {
819
+ // encode forward slashes to prevent them from being mistaken for path separators
820
+ segments[i] = segments[i].replace(/\//g, "%2F");
821
+ }
822
+ }
823
+
824
+ return segments.join("/");
825
+ }
826
+
827
+ /**
828
+ * @ignore
829
+ * Normalizes a URL path by encoding the path segments, query parameters, and hash fragment.
830
+ *
831
+ * - Path segments are encoded using `encodePath`, which encodes each segment individually.
832
+ * - Query parameters (`searchValue`) are converted to a query string using `toKeyValue`.
833
+ * - Hash fragment (`hashValue`) is encoded using `encodeUriSegment` and prefixed with `#`.
834
+ *
835
+ * This function returns a fully constructed URL path with optional query and hash components.
836
+ *
837
+ * @param {string} pathValue - The base URL path (e.g., "folder/item name").
838
+ * @param {Object.<string, any> | string | null} searchValue - An object or string representing query parameters.
839
+ * - If an object, it can contain strings, numbers, booleans, or arrays of values.
840
+ * - If a string, it is assumed to be a raw query string.
841
+ * - If null or undefined, no query string is added.
842
+ * @param {string | null} hashValue - The URL fragment (everything after `#`). If null or undefined, no hash is added.
843
+ *
844
+ * @returns {string} The normalized URL path including encoded path, optional query string, and optional hash.
845
+ *
846
+ * @example
847
+ * normalizePath("products/list", { category: "books", page: 2 }, "section1")
848
+ * // returns "products/list?category=books&page=2#section1"
849
+ *
850
+ * @example
851
+ * normalizePath("user profile/images", null, null)
852
+ * // returns "user%20profile/images"
853
+ */
854
+ export function normalizePath(pathValue, searchValue, hashValue) {
855
+ const search = toKeyValue(searchValue);
856
+ const hash = hashValue ? `#${encodeUriSegment(hashValue)}` : "";
857
+ const path = encodePath(pathValue);
858
+
859
+ return path + (search ? `?${search}` : "") + hash;
860
+ }
861
+
862
+ /**
863
+ * @ignore
864
+ * Parses the application URL and updates the location object with path, search, and hash.
865
+ *
866
+ * @param {string} url - The URL string to parse.
867
+ * @param {boolean} html5Mode - Whether HTML5 mode is enabled (affects decoding).
868
+ * @throws Will throw an error if the URL starts with invalid slashes.
869
+ */
870
+ export function parseAppUrl(url, html5Mode) {
871
+ if (/^\s*[\\/]{2,}/.test(url)) {
872
+ throw $locationMinErr("badpath", 'Invalid url "{0}".', url);
873
+ }
874
+
875
+ const prefixed = url.charAt(0) !== "/";
876
+ if (prefixed) {
877
+ url = `/${url}`;
878
+ }
879
+ const match = urlResolve(url);
880
+ const path =
881
+ prefixed && match.pathname.charAt(0) === "/"
882
+ ? match.pathname.substring(1)
883
+ : match.pathname;
884
+ $$path = decodePath(path, html5Mode);
885
+ $$search = parseKeyValue(match.search);
886
+ $$hash = decodeURIComponent(match.hash);
887
+
888
+ // make sure path starts with '/';
889
+ if ($$path && $$path.charAt(0) !== "/") {
890
+ $$path = `/${$$path}`;
891
+ }
892
+ }
893
+
894
+ /**
895
+ * @ignore
896
+ * Returns the substring of `url` after the `base` string if `url` starts with `base`.
897
+ * Returns `undefined` if `url` does not start with `base`.
898
+ * @param {string} base
899
+ * @param {string} url
900
+ * @returns {string} returns text from `url` after `base` or `undefined` if it does not begin with
901
+ * the expected string.
902
+ */
903
+ export function stripBaseUrl(base, url) {
904
+ if (startsWith(url, base)) {
905
+ return url.substring(base.length);
906
+ }
907
+ }
908
+
909
+ /**
910
+ * @ignore
911
+ * Removes the hash fragment (including the '#') from the given URL string.
912
+ *
913
+ * @param {string} url - The URL string to process.
914
+ * @returns {string} The URL without the hash fragment.
915
+ */
916
+ export function stripHash(url) {
917
+ const index = url.indexOf("#");
918
+ return index === -1 ? url : url.substring(0, index);
919
+ }
920
+
921
+ /**
922
+ * @ignore
923
+ * Removes the file name (and any hash) from a URL, returning the base directory path.
924
+ *
925
+ * For example:
926
+ * - Input: "https://example.com/path/to/file.js"
927
+ * Output: "https://example.com/path/to/"
928
+ *
929
+ * - Input: "https://example.com/path/to/file.js#section"
930
+ * Output: "https://example.com/path/to/"
931
+ *
932
+ * @param {string} url - The URL from which to strip the file name and hash.
933
+ * @returns {string} The base path of the URL, ending with a slash.
934
+ */
935
+ export function stripFile(url) {
936
+ return url.substring(0, stripHash(url).lastIndexOf("/") + 1);
937
+ }
938
+
939
+ /**
940
+ * @ignore
941
+ * Extracts the base server URL (scheme, host, and optional port) from a full URL.
942
+ *
943
+ * If no path is present, returns the full URL.
944
+ *
945
+ * For example:
946
+ * - Input: "https://example.com/path/to/file"
947
+ * Output: "https://example.com"
948
+ *
949
+ * - Input: "http://localhost:3000/api/data"
950
+ * Output: "http://localhost:3000"
951
+ *
952
+ * @param {string} url - The full URL to extract the server base from.
953
+ * @returns {string} The server base, including scheme and host (and port if present).
954
+ */
955
+ export function serverBase(url) {
956
+ const start = url.indexOf("//") + 2;
957
+ const slashIndex = url.indexOf("/", start);
958
+ return slashIndex === -1 ? url : url.substring(0, slashIndex);
959
+ }
960
+
961
+ /**
962
+ * @ignore
963
+ * Determine if two URLs are equal despite potential differences in encoding,
964
+ * trailing slashes, or empty hash fragments, such as between $location.absUrl() and $browser.url().
965
+ *
966
+ * @param {string} a - First URL to compare.
967
+ * @param {string} b - Second URL to compare.
968
+ * @returns {boolean} True if URLs are equivalent after normalization.
969
+ */
970
+ export function urlsEqual(a, b) {
971
+ return normalizeUrl(a) === normalizeUrl(b);
972
+ }
973
+
974
+ /**
975
+ * @ignore
976
+ * Normalize a URL by resolving it via a DOM anchor element,
977
+ * removing trailing slashes (except root), and trimming empty hashes.
978
+ *
979
+ * @param {string} url - URL to normalize.
980
+ * @returns {string} Normalized URL string.
981
+ */
982
+ function normalizeUrl(url) {
983
+ const anchor = document.createElement("a");
984
+ anchor.href = url;
985
+
986
+ let normalized = anchor.href;
987
+
988
+ // Remove trailing slash unless it's root (e.g., https://example.com/)
989
+ if (normalized.endsWith("/") && !/^https?:\/\/[^/]+\/$/.test(normalized)) {
990
+ normalized = normalized.slice(0, -1);
991
+ }
992
+
993
+ // Remove empty hash (e.g., https://example.com/foo# -> https://example.com/foo)
994
+ if (normalized.endsWith("#")) {
995
+ normalized = normalized.slice(0, -1);
996
+ }
997
+
998
+ return normalized;
999
+ }