@phatvu/web-component-poc 1.0.6 → 1.0.7

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 (46) hide show
  1. package/dist/cjs/fast-button.cjs.entry.js +46 -0
  2. package/dist/cjs/{fast-button_4.cjs.entry.js → fast-carousel.cjs.entry.js} +1 -231
  3. package/dist/cjs/fast-input_4.cjs.entry.js +499 -0
  4. package/dist/cjs/{index-B2BTpdbN.js → index-BEvZs91D.js} +2 -2
  5. package/dist/cjs/job-card.cjs.entry.js +1 -1
  6. package/dist/cjs/loader.cjs.js +2 -2
  7. package/dist/cjs/web-component-poc.cjs.js +2 -2
  8. package/dist/collection/collection-manifest.json +3 -1
  9. package/dist/collection/components/fast-input/fast-input.css +55 -0
  10. package/dist/collection/components/fast-input/fast-input.js +335 -0
  11. package/dist/collection/components/jobs-list-only/jobs-list-only.js +180 -3
  12. package/dist/collection/components/jobs-list-reactive/jobs-list-reactive.css +8 -0
  13. package/dist/collection/components/jobs-list-reactive/jobs-list-reactive.js +203 -0
  14. package/dist/components/fast-button.js +1 -1
  15. package/dist/components/fast-carousel.js +1 -1
  16. package/dist/components/fast-input.d.ts +11 -0
  17. package/dist/components/fast-input.js +1 -0
  18. package/dist/components/index.js +1 -1
  19. package/dist/components/job-card.js +1 -1
  20. package/dist/components/jobs-item.js +1 -1
  21. package/dist/components/jobs-list-only.js +1 -1
  22. package/dist/components/jobs-list-reactive.d.ts +11 -0
  23. package/dist/components/jobs-list-reactive.js +1 -0
  24. package/dist/components/{p-ClQDwJJB.js → p-DQiaLjLf.js} +1 -1
  25. package/dist/esm/fast-button.entry.js +44 -0
  26. package/dist/esm/{fast-button_4.entry.js → fast-carousel.entry.js} +2 -229
  27. package/dist/esm/fast-input_4.entry.js +494 -0
  28. package/dist/esm/{index-Dk5CvWmb.js → index-C_ZLQIpp.js} +2 -2
  29. package/dist/esm/job-card.entry.js +1 -1
  30. package/dist/esm/loader.js +3 -3
  31. package/dist/esm/web-component-poc.js +3 -3
  32. package/dist/types/components/fast-input/fast-input.d.ts +37 -0
  33. package/dist/types/components/jobs-list-only/jobs-list-only.d.ts +22 -0
  34. package/dist/types/components/jobs-list-reactive/jobs-list-reactive.d.ts +26 -0
  35. package/dist/types/components.d.ts +223 -0
  36. package/dist/web-component-poc/p-618fba28.entry.js +1 -0
  37. package/dist/web-component-poc/p-7d45772f.entry.js +1 -0
  38. package/dist/web-component-poc/{p-52c85341.entry.js → p-bef7c8e2.entry.js} +1 -1
  39. package/dist/web-component-poc/p-cfb9aed9.entry.js +1 -0
  40. package/dist/web-component-poc/web-component-poc.esm.js +1 -1
  41. package/hydrate/index.js +354 -6
  42. package/hydrate/index.mjs +354 -6
  43. package/package.json +5 -1
  44. package/dist/web-component-poc/p-96761988.entry.js +0 -1
  45. /package/dist/components/{p-UM9TUfe3.js → p-BiaJAQXY.js} +0 -0
  46. /package/dist/web-component-poc/{p-Dk5CvWmb.js → p-C_ZLQIpp.js} +0 -0
package/hydrate/index.js CHANGED
@@ -132,7 +132,7 @@ function hydrateFactory($stencilWindow, $stencilHydrateOpts, $stencilHydrateResu
132
132
 
133
133
 
134
134
  const NAMESPACE = 'web-component-poc';
135
- const BUILD = /* web-component-poc */ { hotModuleReplacement: false, hydratedSelectorName: "hydrated", prop: true, propChangeCallback: false, shadowDom: false, slotRelocation: true, updatable: true};
135
+ const BUILD = /* web-component-poc */ { hotModuleReplacement: false, hydratedSelectorName: "hydrated", propChangeCallback: false, shadowDom: false, slotRelocation: true, state: true, updatable: true};
136
136
 
137
137
  /*
138
138
  Stencil Hydrate Platform v4.43.2 | MIT Licensed | https://stenciljs.com
@@ -4507,7 +4507,7 @@ var registerInstance = (lazyInstance, hostRef) => {
4507
4507
  if (!hostRef) return void 0;
4508
4508
  lazyInstance.__stencil__getHostRef = () => hostRef;
4509
4509
  hostRef.$lazyInstance$ = lazyInstance;
4510
- if (hostRef.$cmpMeta$.$flags$ & 512 /* hasModernPropertyDecls */ && (BUILD.prop)) {
4510
+ if (hostRef.$cmpMeta$.$flags$ & 512 /* hasModernPropertyDecls */ && (BUILD.state)) {
4511
4511
  reWireGetterSetter(lazyInstance, hostRef);
4512
4512
  }
4513
4513
  return hostRef;
@@ -6507,6 +6507,155 @@ class CustomButton {
6507
6507
  }; }
6508
6508
  }
6509
6509
 
6510
+ const fastInputCss = () => `.fast-input{position:relative;display:inline-flex;gap:0.5rem}.fast-input__field{flex:1;padding:0.5rem 0.75rem;font-size:1rem;border:1px solid #ccc;border-radius:4px}.fast-input__button{padding:0.5rem 1rem;font-size:1rem;cursor:pointer;background:#0070f3;color:#fff;border:none;border-radius:4px}.fast-input__dropdown{position:absolute;top:100%;left:0;right:0;margin:0;padding:0;list-style:none;background:#fff;border:1px solid #ccc;border-top:none;border-radius:0 0 4px 4px;z-index:100;max-height:200px;overflow-y:auto}.fast-input__dropdown-item{padding:0.5rem 0.75rem;cursor:pointer}.fast-input__dropdown-item:hover{background:#f0f0f0}.fast-input__dropdown-loading{padding:0.5rem 0.75rem;color:#999;font-style:italic}`;
6511
+
6512
+ class FastInput {
6513
+ constructor(hostRef) {
6514
+ registerInstance(this, hostRef);
6515
+ this.searchExecuted = createEvent(this, "searchExecuted");
6516
+ this.inputChanged = createEvent(this, "inputChanged");
6517
+ }
6518
+ placeholder = 'Search...';
6519
+ value = '';
6520
+ paramName = 'keyword';
6521
+ enableAutocomplete = false;
6522
+ autocompleteUrl = '/api/jobs/autocomplete';
6523
+ targetPath;
6524
+ debounceMs = 300;
6525
+ minChars = 3;
6526
+ searchExecuted;
6527
+ inputChanged;
6528
+ inputValue = '';
6529
+ suggestions = [];
6530
+ showDropdown = false;
6531
+ autocompleteLoading = false;
6532
+ debounceTimer;
6533
+ popstateHandler;
6534
+ connectedCallback() {
6535
+ const urlValue = this.getUrlParam();
6536
+ this.inputValue = urlValue !== null ? urlValue : this.value;
6537
+ this.popstateHandler = () => {
6538
+ this.inputValue = this.getUrlParam() ?? '';
6539
+ };
6540
+ window.addEventListener('popstate', this.popstateHandler);
6541
+ }
6542
+ disconnectedCallback() {
6543
+ window.removeEventListener('popstate', this.popstateHandler);
6544
+ clearTimeout(this.debounceTimer);
6545
+ }
6546
+ getUrlParam() {
6547
+ const params = new URLSearchParams(window.location.search);
6548
+ return params.get(this.paramName);
6549
+ }
6550
+ updateUrlParam(value) {
6551
+ const params = new URLSearchParams(window.location.search);
6552
+ if (value) {
6553
+ params.set(this.paramName, value);
6554
+ }
6555
+ else {
6556
+ params.delete(this.paramName);
6557
+ }
6558
+ const qs = params.toString();
6559
+ const newUrl = qs
6560
+ ? `${window.location.pathname}?${qs}`
6561
+ : window.location.pathname;
6562
+ history.pushState({}, '', newUrl);
6563
+ }
6564
+ submit() {
6565
+ this.updateUrlParam(this.inputValue);
6566
+ document.dispatchEvent(new CustomEvent('search-executed', {
6567
+ detail: { keyword: this.inputValue },
6568
+ bubbles: true,
6569
+ composed: true,
6570
+ }));
6571
+ this.searchExecuted.emit({ keyword: this.inputValue });
6572
+ this.showDropdown = false;
6573
+ }
6574
+ handleInput = (e) => {
6575
+ const value = e.target.value;
6576
+ this.inputValue = value;
6577
+ this.inputChanged.emit({ value });
6578
+ if (this.enableAutocomplete) {
6579
+ this.scheduleAutocomplete(value);
6580
+ }
6581
+ };
6582
+ handleKeydown = (e) => {
6583
+ if (e.key === 'Enter') {
6584
+ this.submit();
6585
+ }
6586
+ else if (e.key === 'Escape') {
6587
+ this.showDropdown = false;
6588
+ }
6589
+ };
6590
+ handleBlur = () => {
6591
+ this.showDropdown = false;
6592
+ };
6593
+ scheduleAutocomplete(value) {
6594
+ clearTimeout(this.debounceTimer);
6595
+ if (value.length < this.minChars) {
6596
+ this.showDropdown = false;
6597
+ return;
6598
+ }
6599
+ this.debounceTimer = setTimeout(() => this.fetchSuggestions(value), this.debounceMs);
6600
+ }
6601
+ async fetchSuggestions(keyword) {
6602
+ if (!this.targetPath) {
6603
+ console.warn('[fast-input] target-path is required for autocomplete');
6604
+ return;
6605
+ }
6606
+ this.autocompleteLoading = true;
6607
+ this.showDropdown = true;
6608
+ try {
6609
+ const res = await fetch(this.autocompleteUrl, {
6610
+ method: 'POST',
6611
+ headers: { 'Content-Type': 'application/json' },
6612
+ body: JSON.stringify({ keyword, target_path: this.targetPath }),
6613
+ });
6614
+ if (!res.ok)
6615
+ throw new Error('autocomplete request failed');
6616
+ const data = await res.json();
6617
+ this.suggestions = data;
6618
+ }
6619
+ catch {
6620
+ this.showDropdown = false;
6621
+ this.suggestions = [];
6622
+ }
6623
+ finally {
6624
+ this.autocompleteLoading = false;
6625
+ }
6626
+ }
6627
+ selectSuggestion(title) {
6628
+ this.inputValue = title;
6629
+ this.showDropdown = false;
6630
+ this.submit();
6631
+ }
6632
+ render() {
6633
+ return (hAsync("div", { key: '3a9d31c7b109205600addc326d63979585f10bcd', class: "fast-input" }, hAsync("input", { key: '8f238fe9e002f367d4939616be8c06d938d76045', type: "text", class: "fast-input__field", placeholder: this.placeholder, value: this.inputValue, onInput: this.handleInput, onKeyDown: this.handleKeydown, onBlur: this.handleBlur }), hAsync("button", { key: '7b7404f13432750ece669da8ce68be15179921de', class: "fast-input__button", type: "button", onClick: () => this.submit() }, "Search"), this.enableAutocomplete && this.showDropdown && (hAsync("ul", { key: '1438bacadc21c183842a8bdaa3f336bffb152e14', class: "fast-input__dropdown" }, this.autocompleteLoading ? (hAsync("li", { class: "fast-input__dropdown-loading" }, "Loading...")) : (this.suggestions.map(s => (hAsync("li", { class: "fast-input__dropdown-item", onMouseDown: e => { e.preventDefault(); }, onClick: () => this.selectSuggestion(s.title) }, s.title))))))));
6634
+ }
6635
+ static get style() { return fastInputCss(); }
6636
+ static get cmpMeta() { return {
6637
+ "$flags$": 512,
6638
+ "$tagName$": "fast-input",
6639
+ "$members$": {
6640
+ "placeholder": [1],
6641
+ "value": [1],
6642
+ "paramName": [1, "param-name"],
6643
+ "enableAutocomplete": [4, "enable-autocomplete"],
6644
+ "autocompleteUrl": [1, "autocomplete-url"],
6645
+ "targetPath": [1, "target-path"],
6646
+ "debounceMs": [2, "debounce-ms"],
6647
+ "minChars": [2, "min-chars"],
6648
+ "inputValue": [32],
6649
+ "suggestions": [32],
6650
+ "showDropdown": [32],
6651
+ "autocompleteLoading": [32]
6652
+ },
6653
+ "$listeners$": undefined,
6654
+ "$lazyBundleId$": "-",
6655
+ "$attrsToReflect$": []
6656
+ }; }
6657
+ }
6658
+
6510
6659
  const jobCardCss = () => `.job-card{display:block;padding:16px;border:1px solid #e0e0e0;border-radius:8px;background-color:#fff;box-shadow:0 2px 4px rgba(0, 0, 0, 0.08);transition:box-shadow 0.2s ease, border-color 0.2s ease}.job-card:hover{box-shadow:0 4px 8px rgba(0, 0, 0, 0.12);border-color:#d0d0d0}.job-card__header{margin-bottom:12px}.job-card__title{margin:0;font-size:18px;font-weight:700;display:flex;align-items:center;flex-wrap:wrap;gap:8px}.job-card__title--link{text-decoration:none;color:#1f9755;transition:color 0.2s ease}.job-card__title--link:hover{text-decoration:underline;color:#1a7a43}.job-card__reference{font-size:0.875em;color:#666;background-color:#f5f5f5;padding:2px 6px;border-radius:3px}.job-card__reference--empty{display:none}.job-card__remote{background:#e8f5e9;color:#2e7d32;border-radius:100px;padding:4px 12px;text-transform:uppercase;font-size:11px;font-weight:700;line-height:1.5;white-space:nowrap}.job-card__remote--empty{display:none}.job-card__distance{display:inline-flex;align-items:center;gap:4px;margin-top:6px;font-size:13px;font-weight:500;color:#555}.job-card__distance--icon{display:inline-flex;align-items:center}.job-card__distance--icon svg{width:16px;height:16px;color:#1f9755}.job-card__content{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.job-card__info{flex:1;display:flex;flex-direction:column;gap:8px}.job-card__street,.job-card__brand,.job-card__employment-type{display:flex;align-items:center;flex-wrap:wrap;gap:4px 6px;font-size:14px}.job-card__street--empty,.job-card__brand--empty,.job-card__employment-type--empty{color:#999}.job-card__street--icon,.job-card__brand--icon,.job-card__employment-type--icon{display:inline-flex;align-items:center;flex-shrink:0}.job-card__street--icon svg,.job-card__brand--icon svg,.job-card__employment-type--icon svg{width:16px;height:16px;color:#666}.job-card__street--label,.job-card__brand--label,.job-card__employment-type--label{color:#333}.job-card__street--label__wrapper{display:flex;align-items:center;gap:6px}.job-card__street--more-locations__wrapper{display:flex;align-items:center;gap:2px;font-size:12px;margin-left:2px}.job-card__street--amount{font-weight:600;color:#1f9755}.job-card__street--more-locations{color:#999}.job-card__apply{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:10px 16px;background-color:#198754;color:#fff;border-radius:4px;text-decoration:none;font-weight:600;font-size:14px;transition:background-color 0.2s ease, transform 0.1s ease;white-space:nowrap;flex-shrink:0}.job-card__apply:hover{background-color:#1a6f47;transform:translateY(-1px)}.job-card__apply:active{transform:translateY(0)}.job-card__apply--icon{display:inline-flex;align-items:center}.job-card__apply--icon svg{width:14px;height:14px}@media (max-width: 768px){.job-card{padding:12px}.job-card__content{flex-direction:column;gap:10px}.job-card__apply{width:100%;justify-content:center}.job-card__title{font-size:16px}}@media (max-width: 480px){.job-card{padding:10px}.job-card__title{font-size:15px}.job-card__distance{font-size:12px}.job-card__street,.job-card__brand,.job-card__employment-type{font-size:13px}}`;
6511
6660
 
6512
6661
  /**
@@ -6829,6 +6978,7 @@ const defaultNoResultsLine2 = 'Please refine your keywords in the search bar abo
6829
6978
  class JobsListOnly {
6830
6979
  constructor(hostRef) {
6831
6980
  registerInstance(this, hostRef);
6981
+ this.fetchComplete = createEvent(this, "fetchComplete");
6832
6982
  }
6833
6983
  /**
6834
6984
  * When "true", use built-in mock data (for local/dev/docs). Otherwise use `jobs` from API/parent.
@@ -6852,16 +7002,84 @@ class JobsListOnly {
6852
7002
  enableKilometers = false;
6853
7003
  /** Extra CSS class on the root element (avoid prop name "class" / "classname" reserved). */
6854
7004
  rootClass = '';
7005
+ /** Template string for count display. Tokens: {count} = jobs on page, {total} = total from API. */
7006
+ showCountText = '';
6855
7007
  showSuggestions = false;
6856
7008
  clearResultSuggestionsTitleText = 'Suggestions';
6857
7009
  clearResultSuggestionsLine1 = 'Try different keywords';
6858
7010
  clearResultSuggestionsLine2 = 'Make sure everything is spelled correctly';
6859
7011
  clearResultSuggestionsLine3 = 'Try other locations';
6860
7012
  clearResultSuggestionsLine4 = '';
7013
+ /** When true, component manages its own data fetching */
7014
+ autoFetch = false;
7015
+ /** Jobs search endpoint */
7016
+ apiUrl = '/api/get-jobs';
7017
+ /** Comma-separated URL param names to watch and forward to the API */
7018
+ watchParams = 'keyword';
7019
+ fetchedJobs = [];
7020
+ fetchedTotal = 0;
7021
+ fetchLoading = false;
7022
+ fetchComplete;
7023
+ searchExecutedHandler;
7024
+ popstateHandler;
7025
+ connectedCallback() {
7026
+ if (this.autoFetch) {
7027
+ this.fetchJobs();
7028
+ this.searchExecutedHandler = () => this.fetchJobs();
7029
+ this.popstateHandler = () => this.fetchJobs();
7030
+ document.addEventListener('search-executed', this.searchExecutedHandler);
7031
+ window.addEventListener('popstate', this.popstateHandler);
7032
+ }
7033
+ }
7034
+ disconnectedCallback() {
7035
+ if (this.autoFetch) {
7036
+ document.removeEventListener('search-executed', this.searchExecutedHandler);
7037
+ window.removeEventListener('popstate', this.popstateHandler);
7038
+ }
7039
+ }
7040
+ async fetchJobs() {
7041
+ this.fetchLoading = true;
7042
+ const params = new URLSearchParams(window.location.search);
7043
+ const watchList = this.watchParams.split(',').map(p => p.trim()).filter(Boolean);
7044
+ const query = new URLSearchParams();
7045
+ for (const key of watchList) {
7046
+ const val = params.get(key);
7047
+ if (val !== null)
7048
+ query.set(key, val);
7049
+ }
7050
+ const url = `${this.apiUrl}?${query.toString()}`;
7051
+ try {
7052
+ const res = await fetch(url, {
7053
+ method: 'POST',
7054
+ headers: { 'Content-Type': 'application/json' },
7055
+ body: JSON.stringify({ disable_switch_search_mode: false }),
7056
+ });
7057
+ if (!res.ok)
7058
+ throw new Error('fetch failed');
7059
+ const data = await res.json();
7060
+ this.fetchedJobs = data.jobs;
7061
+ this.fetchedTotal = data.totalJob;
7062
+ this.fetchComplete.emit({ jobs: data.jobs, totalJob: data.totalJob });
7063
+ }
7064
+ catch {
7065
+ // preserve stale data, just stop loading
7066
+ }
7067
+ finally {
7068
+ this.fetchLoading = false;
7069
+ }
7070
+ }
7071
+ renderCountText(count, total) {
7072
+ return this.showCountText
7073
+ .replace('{count}', String(count))
7074
+ .replace('{total}', String(total));
7075
+ }
6861
7076
  getJobsArray() {
6862
7077
  if (this.mockData) {
6863
7078
  return mockJobsListOnly;
6864
7079
  }
7080
+ if (this.autoFetch) {
7081
+ return this.fetchedJobs;
7082
+ }
6865
7083
  const j = this.jobs;
6866
7084
  if (Array.isArray(j))
6867
7085
  return j;
@@ -6881,11 +7099,15 @@ class JobsListOnly {
6881
7099
  }
6882
7100
  render() {
6883
7101
  const jobsArray = this.getJobsArray();
6884
- const loading = this.mockData ? false : this.loading;
6885
- const totalJob = this.mockData ? jobsArray.length : (this.totalJob || jobsArray.length);
7102
+ const loading = this.mockData ? false : (this.autoFetch ? this.fetchLoading : this.loading);
7103
+ const totalJob = this.mockData
7104
+ ? jobsArray.length
7105
+ : this.autoFetch
7106
+ ? this.fetchedTotal
7107
+ : (this.totalJob || jobsArray.length);
6886
7108
  const showNoResults = !loading && totalJob === 0 && !this.showSuggestions;
6887
7109
  const showSuggestionsBlock = !loading && totalJob === 0 && this.showSuggestions;
6888
- return (hAsync("div", { key: 'c22b59e35668df06633c8c11ae8a51b463e06b19', class: `jobs-list-root ${this.rootClass}`.trim() }, hAsync("div", { key: 'e2b5b1bf68b75dd958b906c18c258faa8f4e1e25', class: "results-container" }, hAsync("div", { key: '923d2402d2951d9de47a69c5fd87a80fda382b6e', class: loading ? 'loader' : 'loader hide', "aria-hidden": !loading }), totalJob > 0 && (hAsync("div", { key: '000c2c2d512f1b48999d628a2517701fc061dd11', class: "card" }, hAsync("ul", { key: '7028fc4f1b007eb9ff9bc205201af986c6d0a9ae', class: "results-list front" }, jobsArray.map((job, index) => this.renderJobItem(job, index))))), showNoResults && (hAsync("div", { key: '4f567c7ddbf7d393469886ce3b4865dcb9f4e761', class: "share-jobs__no-results" }, hAsync("h2", { key: '5f1ae77afeca0c37183dc681a9cc42eebaf0e510' }, this.noResultsLine1), hAsync("h3", { key: '6f8a754181cabf1befc6a9a4cf811417075af918' }, this.noResultsLine2))), showSuggestionsBlock && (hAsync("div", { key: 'a09cc45fbed615bb8672f8e77c2a8793efec2dc5', class: "card primary-color" }, hAsync("h4", { key: '9e53bb60ec086f42845d90c807dbd1cb6a88721d', class: "result-suggestions-title" }, this.clearResultSuggestionsTitleText, ":"), hAsync("ul", { key: '0769ebd6367255dad56e688cd36a65cc41f1f366', class: "results-list front" }, hAsync("li", { key: '7032fc1d77ca040be7d2c949c7bc755c62ef4df3', class: "result-suggestions-line" }, this.clearResultSuggestionsLine1), hAsync("li", { key: '49e0c6d4cad62448abc42124708472d8c268c7d9', class: "result-suggestions-line" }, this.clearResultSuggestionsLine2), hAsync("li", { key: 'ad76dbdad20e8de716613baa2bd1e745b6749fa7', class: "result-suggestions-line" }, this.clearResultSuggestionsLine3), this.clearResultSuggestionsLine4 && (hAsync("li", { key: 'b141b94ceeacd5de8fd430cc9e5e05063953130a', class: "result-suggestions-line" }, this.clearResultSuggestionsLine4))))))));
7110
+ return (hAsync("div", { key: '1116855473d28d650641b9d962243bfcdcb434ec', class: `jobs-list-root ${this.rootClass}`.trim() }, hAsync("div", { key: 'fcef04f1da9ad4e150af9f59921688f5781d9d43', class: "results-container" }, this.autoFetch && this.fetchLoading && (hAsync("div", { key: '75b157c82c89691c7ce73d12ea0144b3b45485c3', class: "jobs-list-only__loading" }, "Loading...")), hAsync("div", { key: 'b09f9879e10ee4a93e32177611912da5f19f3526', class: loading ? 'loader' : 'loader hide', "aria-hidden": !loading }), totalJob > 0 && this.showCountText && (hAsync("p", { key: 'd10f800fb0a33d82531d5f1728bac4ceba2ed577', class: "jobs-list-only__count" }, this.renderCountText(jobsArray.length, totalJob))), totalJob > 0 && (hAsync("div", { key: '18153ed1338bd48f7be4f043b11ce15e3271f27b', class: "card" }, hAsync("ul", { key: '766e128b1fd5adb456530ae39e92ba8eb0b5d6cf', class: "results-list front" }, jobsArray.map((job, index) => this.renderJobItem(job, index))))), showNoResults && (hAsync("div", { key: 'ed6f3d2bd2bbedabd6e69d508ea1425580e6941f', class: "share-jobs__no-results" }, hAsync("h2", { key: '2302656e33340c69e84cb949afb7256b8f35f440' }, this.noResultsLine1), hAsync("h3", { key: '1c7e6642441a96c04ee26883fdec4f81b0fe6cec' }, this.noResultsLine2))), showSuggestionsBlock && (hAsync("div", { key: 'be7af85f64455918545e88952ca6ff00f0a970c5', class: "card primary-color" }, hAsync("h4", { key: '2f63deb8131190eff882308544b15f767b6f3edc', class: "result-suggestions-title" }, this.clearResultSuggestionsTitleText, ":"), hAsync("ul", { key: 'cb18daaa9e2c2c442c5b906ed370dcd653b5262d', class: "results-list front" }, hAsync("li", { key: 'ff1d5c6518b75c0daa35b43df7162b0dfecde25e', class: "result-suggestions-line" }, this.clearResultSuggestionsLine1), hAsync("li", { key: '4099fd7bf8dcf114eca28702a498ab0938f5de46', class: "result-suggestions-line" }, this.clearResultSuggestionsLine2), hAsync("li", { key: 'fb65b54c3c0b14bc58112977eb4c7c56c1246a45', class: "result-suggestions-line" }, this.clearResultSuggestionsLine3), this.clearResultSuggestionsLine4 && (hAsync("li", { key: '10f745e74cf68a2b1c42e6f49f810a8b59eb27b6', class: "result-suggestions-line" }, this.clearResultSuggestionsLine4))))))));
6889
7111
  }
6890
7112
  static get style() { return jobsListOnlyCss(); }
6891
7113
  static get cmpMeta() { return {
@@ -6907,12 +7129,136 @@ class JobsListOnly {
6907
7129
  "remoteLocationText": [1, "remote-location-text"],
6908
7130
  "enableKilometers": [4, "enable-kilometers"],
6909
7131
  "rootClass": [1, "root-class"],
7132
+ "showCountText": [1, "show-count-text"],
6910
7133
  "showSuggestions": [4, "show-suggestions"],
6911
7134
  "clearResultSuggestionsTitleText": [1, "clear-result-suggestions-title-text"],
6912
7135
  "clearResultSuggestionsLine1": [1, "clear-result-suggestions-line-1"],
6913
7136
  "clearResultSuggestionsLine2": [1, "clear-result-suggestions-line-2"],
6914
7137
  "clearResultSuggestionsLine3": [1, "clear-result-suggestions-line-3"],
6915
- "clearResultSuggestionsLine4": [1, "clear-result-suggestions-line-4"]
7138
+ "clearResultSuggestionsLine4": [1, "clear-result-suggestions-line-4"],
7139
+ "autoFetch": [4, "auto-fetch"],
7140
+ "apiUrl": [1, "api-url"],
7141
+ "watchParams": [1, "watch-params"],
7142
+ "fetchedJobs": [32],
7143
+ "fetchedTotal": [32],
7144
+ "fetchLoading": [32]
7145
+ },
7146
+ "$listeners$": undefined,
7147
+ "$lazyBundleId$": "-",
7148
+ "$attrsToReflect$": []
7149
+ }; }
7150
+ }
7151
+
7152
+ const jobsListReactiveCss = () => `jobs-list-reactive{display:block}jobs-list-reactive.loading{opacity:0.6;pointer-events:none}`;
7153
+
7154
+ class JobsListReactive {
7155
+ constructor(hostRef) {
7156
+ registerInstance(this, hostRef);
7157
+ this.fetchComplete = createEvent(this, "fetchComplete");
7158
+ }
7159
+ get el() { return getElement(this); }
7160
+ /** Jobs search endpoint */
7161
+ apiUrl = '/api/get-jobs';
7162
+ /** Comma-separated URL param names to watch and forward to the API */
7163
+ watchParams = 'keyword,location_name';
7164
+ /** CSS class added to container while fetching */
7165
+ loadingClass = 'loading';
7166
+ isLoading = false;
7167
+ fetchComplete;
7168
+ templateEl = null;
7169
+ searchExecutedHandler;
7170
+ popstateHandler;
7171
+ connectedCallback() {
7172
+ this.templateEl = this.el.querySelector('template');
7173
+ this.searchExecutedHandler = () => this.fetchJobs();
7174
+ this.popstateHandler = () => this.fetchJobs();
7175
+ document.addEventListener('search-executed', this.searchExecutedHandler);
7176
+ window.addEventListener('popstate', this.popstateHandler);
7177
+ }
7178
+ disconnectedCallback() {
7179
+ document.removeEventListener('search-executed', this.searchExecutedHandler);
7180
+ window.removeEventListener('popstate', this.popstateHandler);
7181
+ }
7182
+ buildQueryString() {
7183
+ const urlParams = new URLSearchParams(window.location.search);
7184
+ const watchList = this.watchParams.split(',').map(p => p.trim()).filter(Boolean);
7185
+ const query = new URLSearchParams();
7186
+ for (const key of watchList) {
7187
+ const val = urlParams.get(key);
7188
+ if (val !== null && val !== '') {
7189
+ query.set(key, val);
7190
+ }
7191
+ }
7192
+ return query.toString();
7193
+ }
7194
+ async fetchJobs() {
7195
+ this.isLoading = true;
7196
+ this.el.classList.add(this.loadingClass);
7197
+ const queryString = this.buildQueryString();
7198
+ const url = queryString ? `${this.apiUrl}?${queryString}` : this.apiUrl;
7199
+ try {
7200
+ const res = await fetch(url, {
7201
+ method: 'POST',
7202
+ headers: { 'Content-Type': 'application/json' },
7203
+ body: JSON.stringify({ disable_switch_search_mode: false }),
7204
+ });
7205
+ if (!res.ok)
7206
+ throw new Error('fetch failed');
7207
+ const data = await res.json();
7208
+ this.renderJobs(data.jobs);
7209
+ this.updateCountElements(data.jobs.length, data.totalJob);
7210
+ this.fetchComplete.emit({ jobs: data.jobs, totalJob: data.totalJob });
7211
+ }
7212
+ catch {
7213
+ // Preserve stale data on error
7214
+ }
7215
+ finally {
7216
+ this.isLoading = false;
7217
+ this.el.classList.remove(this.loadingClass);
7218
+ }
7219
+ }
7220
+ renderJobs(jobs) {
7221
+ if (!this.templateEl)
7222
+ return;
7223
+ // Remove all children except the template
7224
+ const children = Array.from(this.el.children);
7225
+ for (const child of children) {
7226
+ if (child !== this.templateEl) {
7227
+ child.remove();
7228
+ }
7229
+ }
7230
+ // Clone template and render each job
7231
+ for (const job of jobs) {
7232
+ const clone = this.templateEl.content.cloneNode(true);
7233
+ const jobCard = clone.querySelector('job-card');
7234
+ if (jobCard) {
7235
+ jobCard.setAttribute('job', JSON.stringify(job));
7236
+ }
7237
+ this.el.appendChild(clone);
7238
+ }
7239
+ }
7240
+ updateCountElements(count, total) {
7241
+ const countEls = document.querySelectorAll('[data-job-count]');
7242
+ const totalEls = document.querySelectorAll('[data-job-total]');
7243
+ countEls.forEach(el => {
7244
+ el.textContent = String(count);
7245
+ });
7246
+ totalEls.forEach(el => {
7247
+ el.textContent = String(total);
7248
+ });
7249
+ }
7250
+ render() {
7251
+ return hAsync("slot", { key: '30a6fe9727eb877b6aafb99072c40811df121ba6' });
7252
+ }
7253
+ static get style() { return jobsListReactiveCss(); }
7254
+ static get cmpMeta() { return {
7255
+ "$flags$": 772,
7256
+ "$tagName$": "jobs-list-reactive",
7257
+ "$members$": {
7258
+ "apiUrl": [1, "api-url"],
7259
+ "watchParams": [1, "watch-params"],
7260
+ "loadingClass": [1, "loading-class"],
7261
+ "isLoading": [32]
6916
7262
  },
6917
7263
  "$listeners$": undefined,
6918
7264
  "$lazyBundleId$": "-",
@@ -6923,9 +7269,11 @@ class JobsListOnly {
6923
7269
  registerComponents([
6924
7270
  AppCarousel,
6925
7271
  CustomButton,
7272
+ FastInput,
6926
7273
  JobCard,
6927
7274
  JobsItem,
6928
7275
  JobsListOnly,
7276
+ JobsListReactive,
6929
7277
  ]);
6930
7278
 
6931
7279
  exports.hydrateApp = hydrateApp;