@phatvu/web-component-poc 1.0.5 → 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 (55) 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 +138 -0
  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 +4 -1
  9. package/dist/collection/components/button/button.js +2 -2
  10. package/dist/collection/components/fast-input/fast-input.css +55 -0
  11. package/dist/collection/components/fast-input/fast-input.js +335 -0
  12. package/dist/collection/components/job-card/job-card.css +247 -0
  13. package/dist/collection/components/job-card/job-card.js +435 -0
  14. package/dist/collection/components/jobs-item/jobs-item.js +5 -5
  15. package/dist/collection/components/jobs-list-only/jobs-list-only.js +185 -8
  16. package/dist/collection/components/jobs-list-reactive/jobs-list-reactive.css +8 -0
  17. package/dist/collection/components/jobs-list-reactive/jobs-list-reactive.js +203 -0
  18. package/dist/components/fast-button.js +1 -1
  19. package/dist/components/fast-carousel.js +1 -1
  20. package/dist/components/fast-input.d.ts +11 -0
  21. package/dist/components/fast-input.js +1 -0
  22. package/dist/components/index.js +1 -1
  23. package/dist/components/job-card.d.ts +11 -0
  24. package/dist/components/job-card.js +1 -0
  25. package/dist/components/jobs-item.js +1 -1
  26. package/dist/components/jobs-list-only.js +1 -1
  27. package/dist/components/jobs-list-reactive.d.ts +11 -0
  28. package/dist/components/jobs-list-reactive.js +1 -0
  29. package/dist/components/{p-ClQDwJJB.js → p-DQiaLjLf.js} +1 -1
  30. package/dist/esm/fast-button.entry.js +44 -0
  31. package/dist/esm/{fast-button_4.entry.js → fast-carousel.entry.js} +2 -229
  32. package/dist/esm/fast-input_4.entry.js +494 -0
  33. package/dist/esm/{index-Dk5CvWmb.js → index-C_ZLQIpp.js} +2 -2
  34. package/dist/esm/job-card.entry.js +136 -0
  35. package/dist/esm/loader.js +3 -3
  36. package/dist/esm/web-component-poc.js +3 -3
  37. package/dist/types/components/fast-input/fast-input.d.ts +37 -0
  38. package/dist/types/components/job-card/job-card.d.ts +93 -0
  39. package/dist/types/components/jobs-item/jobs-item.d.ts +2 -2
  40. package/dist/types/components/jobs-list-only/jobs-list-only.d.ts +24 -2
  41. package/dist/types/components/jobs-list-reactive/jobs-list-reactive.d.ts +26 -0
  42. package/dist/types/components.d.ts +469 -7
  43. package/dist/types/mock/jobs-list-only.mock.d.ts +2 -2
  44. package/dist/types/types/jobs-list.d.ts +6 -2
  45. package/dist/web-component-poc/p-618fba28.entry.js +1 -0
  46. package/dist/web-component-poc/p-7d45772f.entry.js +1 -0
  47. package/dist/web-component-poc/p-bef7c8e2.entry.js +1 -0
  48. package/dist/web-component-poc/p-cfb9aed9.entry.js +1 -0
  49. package/dist/web-component-poc/web-component-poc.esm.js +1 -1
  50. package/hydrate/index.js +534 -6
  51. package/hydrate/index.mjs +534 -6
  52. package/package.json +9 -1
  53. package/dist/web-component-poc/p-df843533.entry.js +0 -1
  54. /package/dist/components/{p-UM9TUfe3.js → p-BiaJAQXY.js} +0 -0
  55. /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,334 @@ 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
+
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}}`;
6660
+
6661
+ /**
6662
+ * Helper function to get formatted location label from location object
6663
+ */
6664
+ function getLocationLabel$1(loc) {
6665
+ if (loc.cityStateAbbr)
6666
+ return loc.cityStateAbbr;
6667
+ const parts = [loc.streetAddress, loc.city, loc.stateAbbr || loc.state, loc.countryAbbr || loc.country].filter(Boolean);
6668
+ return parts.join(', ') || loc.locationText || '';
6669
+ }
6670
+ /**
6671
+ * Helper function to get the first location from job locations
6672
+ */
6673
+ function getFirstLocation$1(job) {
6674
+ const locs = job.locations;
6675
+ if (!locs?.length)
6676
+ return undefined;
6677
+ return locs[0];
6678
+ }
6679
+ /**
6680
+ * JobCard Component
6681
+ *
6682
+ * A reusable card component for displaying job information based on the React JobItem component.
6683
+ * Features include:
6684
+ * - Job title with apply link
6685
+ * - Location with distance
6686
+ * - Brand/company name
6687
+ * - Employment type
6688
+ * - Reference number (optional)
6689
+ * - Remote indicator (optional)
6690
+ * - Multi-location support
6691
+ * - Custom extra fields
6692
+ *
6693
+ * @example
6694
+ * ```html
6695
+ * <job-card
6696
+ * .job={jobData}
6697
+ * applyButtonText="Apply"
6698
+ * showBrand="true"
6699
+ * showEmploymentType="true"
6700
+ * ></job-card>
6701
+ * ```
6702
+ */
6703
+ class JobCard {
6704
+ constructor(hostRef) {
6705
+ registerInstance(this, hostRef);
6706
+ }
6707
+ /**
6708
+ * The job data object to display. Accepts either a Job object or a JSON string.
6709
+ */
6710
+ job;
6711
+ /**
6712
+ * Index of the job in a list (used for accessibility)
6713
+ */
6714
+ index = 0;
6715
+ /**
6716
+ * Text for the apply button
6717
+ */
6718
+ applyButtonText = 'Apply Now';
6719
+ /**
6720
+ * Whether to show the brand/company name
6721
+ */
6722
+ showBrand = true;
6723
+ /**
6724
+ * Whether to show the reference number
6725
+ */
6726
+ showReference = false;
6727
+ /**
6728
+ * Whether to show employment type
6729
+ */
6730
+ showEmploymentType = true;
6731
+ /**
6732
+ * Text shown for multiple locations
6733
+ */
6734
+ multiLocationText = 'More locations';
6735
+ /**
6736
+ * Text shown for remote jobs
6737
+ */
6738
+ remoteLocationText = 'Remote';
6739
+ /**
6740
+ * Whether to show distances in kilometers instead of miles
6741
+ */
6742
+ enableKilometers = false;
6743
+ /**
6744
+ * Whether to show commute time
6745
+ */
6746
+ showCommuteTime = false;
6747
+ /**
6748
+ * Format string for street address (not used in base web component)
6749
+ */
6750
+ streetFormat = '{street}, {city_state_abbr}';
6751
+ /** Extra CSS class on the root inner element (avoid prop name "className", reserved on HTMLElement). */
6752
+ rootClass = '';
6753
+ /**
6754
+ * Custom extra fields configuration
6755
+ */
6756
+ extraFieldsConfig = [];
6757
+ /**
6758
+ * Format distance with unit
6759
+ */
6760
+ formatDistance(distance) {
6761
+ const value = this.enableKilometers ? distance * 1.60934 : distance;
6762
+ const unit = this.enableKilometers ? 'Km' : 'Miles';
6763
+ const str = `${value.toFixed(1)} ${unit}`.replace('.0', '');
6764
+ return str;
6765
+ }
6766
+ /**
6767
+ * Is Empty utility
6768
+ */
6769
+ isEmpty(value) {
6770
+ if (value === null || value === undefined || value === '') {
6771
+ return true;
6772
+ }
6773
+ if (Array.isArray(value) && value.length === 0) {
6774
+ return true;
6775
+ }
6776
+ return false;
6777
+ }
6778
+ /**
6779
+ * Parse job data from prop - handles both object and JSON string
6780
+ */
6781
+ getJobData() {
6782
+ if (!this.job)
6783
+ return null;
6784
+ if (typeof this.job === 'string') {
6785
+ try {
6786
+ return JSON.parse(this.job);
6787
+ }
6788
+ catch {
6789
+ console.warn('job-card: Failed to parse job JSON string');
6790
+ return null;
6791
+ }
6792
+ }
6793
+ return this.job;
6794
+ }
6795
+ render() {
6796
+ const job = this.getJobData();
6797
+ if (!job)
6798
+ return null;
6799
+ const firstLoc = getFirstLocation$1(job);
6800
+ const locationLabel = firstLoc ? getLocationLabel$1(firstLoc) : '';
6801
+ const distance = firstLoc?.distance ?? 0;
6802
+ const distanceLabel = distance > 0 ? this.formatDistance(distance) : '';
6803
+ const applyHref = job.applyURL ||
6804
+ (job.originalURL ? `${typeof window !== 'undefined' ? window.location.origin : ''}${job.originalURL}` : '#');
6805
+ const applyLabel = `${this.applyButtonText}, ${job.title || ''}`;
6806
+ const locs = job.locations ?? [];
6807
+ const hasMultipleLocations = locs.length > 1;
6808
+ return (hAsync("div", { class: `job-card ${this.rootClass}`.trim() }, hAsync("div", { class: "job-card__header" }, hAsync("h3", { class: "job-card__title" }, hAsync("a", { class: "job-card__title--link", href: applyHref, target: "_blank", rel: "noopener noreferrer" }, job.title || ''), this.showReference && (hAsync("span", { class: `job-card__reference ${job.reference ? '' : 'job-card__reference--empty'}` }, job.reference || '')), job.isRemote && (hAsync("span", { class: this.remoteLocationText ? 'job-card__remote' : 'job-card__remote job-card__remote--empty' }, this.remoteLocationText))), distanceLabel && (hAsync("div", { class: "job-card__distance" }, hAsync("span", { class: "job-card__distance--icon" }, hAsync("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5" }, hAsync("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18z" }), hAsync("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M12 8v4l2 2" }))), hAsync("span", { class: "job-card__distance--label" }, distanceLabel)))), hAsync("div", { class: "job-card__content" }, hAsync("div", { class: "job-card__info" }, hAsync("div", { class: locs.length ? 'job-card__street' : 'job-card__street job-card__street--empty' }, hAsync("div", { class: "job-card__street--label__wrapper" }, hAsync("span", { class: "job-card__street--icon" }, hAsync("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5" }, hAsync("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0z" }), hAsync("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1 1 15 0z" }))), hAsync("span", { class: "job-card__street--label" }, locationLabel || '—')), hasMultipleLocations && (hAsync("div", { class: "job-card__street--more-locations__wrapper" }, hAsync("span", { class: "job-card__street--amount" }, "+", locs.length - 1), hAsync("span", { class: "job-card__street--more-locations" }, this.multiLocationText)))), this.showBrand && (hAsync("div", { class: job.brandName ? 'job-card__brand' : 'job-card__brand job-card__brand--empty' }, hAsync("span", { class: "job-card__brand--icon" }, hAsync("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5" }, hAsync("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M2.25 21h19.5m-18-18v18m10.5-18v18m6-13.5V21M6.75 6.75h.75m-.75 3h.75m-.75 3h.75m3-6h.75m-.75 3h.75m-.75 3h.75M6.75 21v-3.375c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21M3 3h12m-.75 4.5H21m-3.75 3.75h.008v.008h-.008v-.008zm0 3h.008v.008h-.008v-.008zm0 3h.008v.008h-.008v-.008z" }))), hAsync("span", { class: "job-card__brand--label" }, job.brandName || '—'))), this.showEmploymentType && (hAsync("div", { class: job.employmentType?.length
6809
+ ? 'job-card__employment-type'
6810
+ : 'job-card__employment-type job-card__employment-type--empty' }, hAsync("span", { class: "job-card__employment-type--icon" }, hAsync("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5" }, hAsync("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0z" }))), job.employmentType?.length ? (job.employmentType?.map((type) => (hAsync("span", { key: type, class: "job-card__employment-type--label" }, type)))) : (hAsync("span", { class: "job-card__employment-type--label" }, "\u2014")))), (job.jobCardExtraFields ?? []).length > 0 &&
6811
+ job.jobCardExtraFields?.map((field, idx) => (hAsync("div", { key: idx, class: !this.isEmpty(field.value) ? `${field.classname}` : `${field.classname}--empty` }, Array.isArray(field.value) ? (field.value.map((item, itemIdx) => (hAsync("span", { key: itemIdx, class: `${field.classname}--label` }, item)))) : (hAsync("span", { class: `${field.classname}--label` }, field.value)))))), hAsync("a", { class: "job-card__apply", href: applyHref, target: "_blank", rel: "noopener noreferrer", "aria-label": applyLabel }, hAsync("span", { class: "job-card__apply--label" }, this.applyButtonText), hAsync("span", { class: "job-card__apply--icon" }, hAsync("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, hAsync("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" })))))));
6812
+ }
6813
+ static get style() { return jobCardCss(); }
6814
+ static get cmpMeta() { return {
6815
+ "$flags$": 512,
6816
+ "$tagName$": "job-card",
6817
+ "$members$": {
6818
+ "job": [1],
6819
+ "index": [2],
6820
+ "applyButtonText": [1, "apply-button-text"],
6821
+ "showBrand": [4, "show-brand"],
6822
+ "showReference": [4, "show-reference"],
6823
+ "showEmploymentType": [4, "show-employment-type"],
6824
+ "multiLocationText": [1, "multi-location-text"],
6825
+ "remoteLocationText": [1, "remote-location-text"],
6826
+ "enableKilometers": [4, "enable-kilometers"],
6827
+ "showCommuteTime": [4, "show-commute-time"],
6828
+ "streetFormat": [1, "street-format"],
6829
+ "rootClass": [1, "root-class"],
6830
+ "extraFieldsConfig": [16]
6831
+ },
6832
+ "$listeners$": undefined,
6833
+ "$lazyBundleId$": "-",
6834
+ "$attrsToReflect$": []
6835
+ }; }
6836
+ }
6837
+
6510
6838
  const jobsItemCss = () => `.results-list__item{list-style:none;padding:10px 0;border-bottom:1px solid #ddd;margin:15px 0;display:inline-block;width:100%;position:relative}.results-list__item:last-child{border-bottom:none}.results-list__item-header{margin:10px 0;font-size:18px;font-weight:700;display:flex;flex-direction:column}.results-list__item-title{margin:0}.results-list__item-title--link{text-decoration:none;color:#1f9755}.results-list__item-title--link:hover{text-decoration:underline}.reference{margin-left:8px;font-size:0.9em;color:#666}.reference.empty{display:none}.remote{background:#f3f3f3;color:#808285;border-radius:100px;padding:6px 16px;text-transform:uppercase;font-size:12px;font-weight:700;line-height:24px;margin-left:8px}.remote--empty{display:none}.results-list__item-distance{display:inline-flex;align-items:center;gap:4px;margin-top:4px;font-size:14px;font-weight:400}.results-list__item-distance--icon{display:inline-flex}.results-list__item-distance--icon svg{width:16px;height:16px}.results-list__item-content{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-top:8px}.results-list__item-info{flex:1}.results-list__item-street,.results-list__item-brand,.results-list__item-employment-type{margin:10px 0;display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px}.results-list__item-street--empty,.results-list__item-brand--empty,.results-list__item-employment-type--empty{color:#999}.results-list__item-street--icon,.results-list__item-brand--icon,.results-list__item-employment-type--icon{margin-right:6px;display:inline-flex}.results-list__item-street--icon svg,.results-list__item-brand--icon svg,.results-list__item-employment-type--icon svg{width:16px;height:16px}.results-list__item-street--more-locations__wrapper{margin-left:8px}.results-list__item-street--amount{font-weight:600}.results-list__item-apply{margin:10px 0;padding:10px 20px;display:inline-flex;align-items:center;gap:8px;background-color:#198754;color:#fff;border-radius:3px;text-decoration:none;font-weight:600;flex-shrink:0}.results-list__item-apply:hover{background-color:#1f9755;color:#fff}.results-list__item-apply--icon svg{width:14px;height:14px}`;
6511
6839
 
6512
6840
  function getLocationLabel(loc) {
@@ -6650,6 +6978,7 @@ const defaultNoResultsLine2 = 'Please refine your keywords in the search bar abo
6650
6978
  class JobsListOnly {
6651
6979
  constructor(hostRef) {
6652
6980
  registerInstance(this, hostRef);
6981
+ this.fetchComplete = createEvent(this, "fetchComplete");
6653
6982
  }
6654
6983
  /**
6655
6984
  * When "true", use built-in mock data (for local/dev/docs). Otherwise use `jobs` from API/parent.
@@ -6673,16 +7002,84 @@ class JobsListOnly {
6673
7002
  enableKilometers = false;
6674
7003
  /** Extra CSS class on the root element (avoid prop name "class" / "classname" reserved). */
6675
7004
  rootClass = '';
7005
+ /** Template string for count display. Tokens: {count} = jobs on page, {total} = total from API. */
7006
+ showCountText = '';
6676
7007
  showSuggestions = false;
6677
7008
  clearResultSuggestionsTitleText = 'Suggestions';
6678
7009
  clearResultSuggestionsLine1 = 'Try different keywords';
6679
7010
  clearResultSuggestionsLine2 = 'Make sure everything is spelled correctly';
6680
7011
  clearResultSuggestionsLine3 = 'Try other locations';
6681
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
+ }
6682
7076
  getJobsArray() {
6683
7077
  if (this.mockData) {
6684
7078
  return mockJobsListOnly;
6685
7079
  }
7080
+ if (this.autoFetch) {
7081
+ return this.fetchedJobs;
7082
+ }
6686
7083
  const j = this.jobs;
6687
7084
  if (Array.isArray(j))
6688
7085
  return j;
@@ -6702,11 +7099,15 @@ class JobsListOnly {
6702
7099
  }
6703
7100
  render() {
6704
7101
  const jobsArray = this.getJobsArray();
6705
- const loading = this.mockData ? false : this.loading;
6706
- 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);
6707
7108
  const showNoResults = !loading && totalJob === 0 && !this.showSuggestions;
6708
7109
  const showSuggestionsBlock = !loading && totalJob === 0 && this.showSuggestions;
6709
- return (hAsync("div", { key: '9e69d40c649b4f16617f522c89bc87d4cbffe1e3', class: `jobs-list-root ${this.rootClass}`.trim() }, hAsync("div", { key: '7daf1c09dd1d5553a335f3c81c894a331964ee96', class: "results-container" }, hAsync("div", { key: '9ab3db2b834359a64faee61f8c3dc2daca8211d9', class: loading ? 'loader' : 'loader hide', "aria-hidden": !loading }), totalJob > 0 && (hAsync("div", { key: 'c729ccdf715105b9fbc7af0a1f148c82535772c1', class: "card" }, hAsync("ul", { key: 'b0c5b6baccf7b94819cc2ad9ab970981a5294b42', class: "results-list front" }, jobsArray.map((job, index) => this.renderJobItem(job, index))))), showNoResults && (hAsync("div", { key: '8f1684ddde51474e9c1699f93fb01c56e5406a51', class: "share-jobs__no-results" }, hAsync("h2", { key: '2e3bc4b6b7647e3939588102d659e777207e46e9' }, this.noResultsLine1), hAsync("h3", { key: 'f4881f19ad5db8fdd47aa4d31e1e0435f08da1ab' }, this.noResultsLine2))), showSuggestionsBlock && (hAsync("div", { key: 'ed34907fd90f28571786045f21525895cef6e98a', class: "card primary-color" }, hAsync("h4", { key: '33e96147f1b427df874656c1e0647af1d97fea29', class: "result-suggestions-title" }, this.clearResultSuggestionsTitleText, ":"), hAsync("ul", { key: '570fd8e5068f1a7c809fad47347fe5f469da8bc6', class: "results-list front" }, hAsync("li", { key: 'b5d22dfb448d2888dc010d6e494d1834d3d60034', class: "result-suggestions-line" }, this.clearResultSuggestionsLine1), hAsync("li", { key: '77c1e8bbe9ab238e4f05fdbaf43551aef4f2a36e', class: "result-suggestions-line" }, this.clearResultSuggestionsLine2), hAsync("li", { key: '0bd9503bf686213ae087f74a7b5f2cc62f9c96b7', class: "result-suggestions-line" }, this.clearResultSuggestionsLine3), this.clearResultSuggestionsLine4 && (hAsync("li", { key: '961b2904347e1c5fc6b355a069790f0f2997917f', 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))))))));
6710
7111
  }
6711
7112
  static get style() { return jobsListOnlyCss(); }
6712
7113
  static get cmpMeta() { return {
@@ -6728,12 +7129,136 @@ class JobsListOnly {
6728
7129
  "remoteLocationText": [1, "remote-location-text"],
6729
7130
  "enableKilometers": [4, "enable-kilometers"],
6730
7131
  "rootClass": [1, "root-class"],
7132
+ "showCountText": [1, "show-count-text"],
6731
7133
  "showSuggestions": [4, "show-suggestions"],
6732
7134
  "clearResultSuggestionsTitleText": [1, "clear-result-suggestions-title-text"],
6733
7135
  "clearResultSuggestionsLine1": [1, "clear-result-suggestions-line-1"],
6734
7136
  "clearResultSuggestionsLine2": [1, "clear-result-suggestions-line-2"],
6735
7137
  "clearResultSuggestionsLine3": [1, "clear-result-suggestions-line-3"],
6736
- "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]
6737
7262
  },
6738
7263
  "$listeners$": undefined,
6739
7264
  "$lazyBundleId$": "-",
@@ -6744,8 +7269,11 @@ class JobsListOnly {
6744
7269
  registerComponents([
6745
7270
  AppCarousel,
6746
7271
  CustomButton,
7272
+ FastInput,
7273
+ JobCard,
6747
7274
  JobsItem,
6748
7275
  JobsListOnly,
7276
+ JobsListReactive,
6749
7277
  ]);
6750
7278
 
6751
7279
  exports.hydrateApp = hydrateApp;