@phatvu/web-component-poc 1.0.6 → 1.0.8

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 (52) hide show
  1. package/dist/cjs/fast-button_6.cjs.entry.js +578 -0
  2. package/dist/cjs/{fast-button_4.cjs.entry.js → fast-carousel.cjs.entry.js} +1 -231
  3. package/dist/cjs/{index-B2BTpdbN.js → index-227GpI8K.js} +66 -2
  4. package/dist/cjs/job-card.cjs.entry.js +1 -1
  5. package/dist/cjs/loader.cjs.js +2 -2
  6. package/dist/cjs/web-component-poc.cjs.js +2 -2
  7. package/dist/collection/collection-manifest.json +4 -1
  8. package/dist/collection/components/button/button.css +14 -14
  9. package/dist/collection/components/button/button.js +12 -24
  10. package/dist/collection/components/fast-form/fast-form.css +5 -0
  11. package/dist/collection/components/fast-form/fast-form.js +99 -0
  12. package/dist/collection/components/fast-input/fast-input.css +45 -0
  13. package/dist/collection/components/fast-input/fast-input.js +359 -0
  14. package/dist/collection/components/jobs-list-only/jobs-list-only.js +180 -3
  15. package/dist/collection/components/jobs-list-reactive/jobs-list-reactive.css +8 -0
  16. package/dist/collection/components/jobs-list-reactive/jobs-list-reactive.js +203 -0
  17. package/dist/components/fast-button.js +1 -1
  18. package/dist/components/fast-carousel.js +1 -1
  19. package/dist/components/fast-form.d.ts +11 -0
  20. package/dist/components/fast-form.js +1 -0
  21. package/dist/components/fast-input.d.ts +11 -0
  22. package/dist/components/fast-input.js +1 -0
  23. package/dist/components/index.js +1 -1
  24. package/dist/components/job-card.js +1 -1
  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-Bb27ylcX.js +1 -0
  30. package/dist/components/{p-ClQDwJJB.js → p-CzgtwPsc.js} +1 -1
  31. package/dist/esm/fast-button_6.entry.js +571 -0
  32. package/dist/esm/{fast-button_4.entry.js → fast-carousel.entry.js} +2 -229
  33. package/dist/esm/{index-Dk5CvWmb.js → index-BqjrT3zA.js} +66 -2
  34. package/dist/esm/job-card.entry.js +1 -1
  35. package/dist/esm/loader.js +3 -3
  36. package/dist/esm/web-component-poc.js +3 -3
  37. package/dist/types/components/button/button.d.ts +1 -13
  38. package/dist/types/components/fast-form/fast-form.d.ts +10 -0
  39. package/dist/types/components/fast-input/fast-input.d.ts +35 -0
  40. package/dist/types/components/jobs-list-only/jobs-list-only.d.ts +22 -0
  41. package/dist/types/components/jobs-list-reactive/jobs-list-reactive.d.ts +26 -0
  42. package/dist/types/components.d.ts +256 -11
  43. package/dist/web-component-poc/{p-52c85341.entry.js → p-14247159.entry.js} +1 -1
  44. package/dist/web-component-poc/p-309a490b.entry.js +1 -0
  45. package/dist/web-component-poc/p-7ea9a87f.entry.js +1 -0
  46. package/dist/web-component-poc/{p-Dk5CvWmb.js → p-BqjrT3zA.js} +2 -2
  47. package/dist/web-component-poc/web-component-poc.esm.js +1 -1
  48. package/hydrate/index.js +457 -27
  49. package/hydrate/index.mjs +457 -27
  50. package/package.json +7 -3
  51. package/dist/components/p-UM9TUfe3.js +0 -1
  52. package/dist/web-component-poc/p-96761988.entry.js +0 -1
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
@@ -2099,6 +2099,13 @@ var dispatchHooks = (hostRef, isInitialLoad) => {
2099
2099
  hostRef.$deferredConnectedCallback$ = false;
2100
2100
  safeCall(instance, "connectedCallback", void 0, elm);
2101
2101
  }
2102
+ {
2103
+ hostRef.$flags$ |= 256 /* isListenReady */;
2104
+ if (hostRef.$queuedListeners$) {
2105
+ hostRef.$queuedListeners$.map(([methodName, event]) => safeCall(instance, methodName, event, elm));
2106
+ hostRef.$queuedListeners$ = void 0;
2107
+ }
2108
+ }
2102
2109
  if (hostRef.$fetchedCbList$.length) {
2103
2110
  hostRef.$fetchedCbList$.forEach((cb) => cb(elm));
2104
2111
  }
@@ -2478,6 +2485,7 @@ var connectedCallback = (elm) => {
2478
2485
  initializeComponent(elm, hostRef, cmpMeta);
2479
2486
  }
2480
2487
  } else {
2488
+ addHostEventListeners(elm, hostRef, cmpMeta.$listeners$);
2481
2489
  if (hostRef == null ? void 0 : hostRef.$lazyInstance$) {
2482
2490
  fireConnectedCallback(hostRef.$lazyInstance$, elm);
2483
2491
  } else if (hostRef == null ? void 0 : hostRef.$onReadyPromise$) {
@@ -2497,6 +2505,32 @@ var setContentReference = (elm) => {
2497
2505
  contentRefElm["s-cn"] = true;
2498
2506
  insertBefore(elm, contentRefElm, elm.firstChild);
2499
2507
  };
2508
+ var addHostEventListeners = (elm, hostRef, listeners, attachParentListeners) => {
2509
+ if (listeners && win.document) {
2510
+ listeners.map(([flags, name, method]) => {
2511
+ const target = elm;
2512
+ const handler = hostListenerProxy(hostRef, method);
2513
+ const opts = hostListenerOpts(flags);
2514
+ plt.ael(target, name, handler, opts);
2515
+ (hostRef.$rmListeners$ = hostRef.$rmListeners$ || []).push(() => plt.rel(target, name, handler, opts));
2516
+ });
2517
+ }
2518
+ };
2519
+ var hostListenerProxy = (hostRef, methodName) => (ev) => {
2520
+ var _a;
2521
+ try {
2522
+ {
2523
+ if (hostRef.$flags$ & 256 /* isListenReady */) {
2524
+ (_a = hostRef.$lazyInstance$) == null ? void 0 : _a[methodName](ev);
2525
+ } else {
2526
+ (hostRef.$queuedListeners$ = hostRef.$queuedListeners$ || []).push([methodName, ev]);
2527
+ }
2528
+ }
2529
+ } catch (e) {
2530
+ consoleError(e, hostRef.$hostElement$);
2531
+ }
2532
+ };
2533
+ var hostListenerOpts = (flags) => (flags & 2 /* Capture */) !== 0;
2500
2534
  function transformTag(tag) {
2501
2535
  return tag;
2502
2536
  }
@@ -4277,6 +4311,7 @@ async function hydrateComponent(win2, results, tagName, elm, waitingElements) {
4277
4311
  if (!hostRef) {
4278
4312
  return;
4279
4313
  }
4314
+ addHostEventListeners(this, hostRef, cmpMeta.$listeners$);
4280
4315
  try {
4281
4316
  connectedCallback(elm);
4282
4317
  await elm.componentOnReady();
@@ -4507,7 +4542,7 @@ var registerInstance = (lazyInstance, hostRef) => {
4507
4542
  if (!hostRef) return void 0;
4508
4543
  lazyInstance.__stencil__getHostRef = () => hostRef;
4509
4544
  hostRef.$lazyInstance$ = lazyInstance;
4510
- if (hostRef.$cmpMeta$.$flags$ & 512 /* hasModernPropertyDecls */ && (BUILD.prop)) {
4545
+ if (hostRef.$cmpMeta$.$flags$ & 512 /* hasModernPropertyDecls */ && (BUILD.state)) {
4511
4546
  reWireGetterSetter(lazyInstance, hostRef);
4512
4547
  }
4513
4548
  return hostRef;
@@ -6454,28 +6489,16 @@ class AppCarousel {
6454
6489
  }; }
6455
6490
  }
6456
6491
 
6457
- const buttonCss = () => `:host{display:inline-block}.custom-button{display:inline-flex;align-items:center;justify-content:center;padding:0.5rem 1rem;font-family:inherit;font-size:0.875rem;font-weight:500;line-height:1.25;border:none;border-radius:0.375rem;cursor:pointer;transition:background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease}.custom-button:focus{outline:2px solid var(--custom-button-focus-ring, #2563eb);outline-offset:2px}.custom-button:focus:not(:focus-visible){outline:none}.custom-button--primary{background-color:var(--custom-button-primary-bg, #2563eb);color:var(--custom-button-primary-color, #fff)}.custom-button--primary:hover:not(.custom-button--disabled){background-color:var(--custom-button-primary-hover-bg, #1d4ed8)}.custom-button--primary:active:not(.custom-button--disabled){background-color:var(--custom-button-primary-active-bg, #1e40af)}.custom-button--secondary{background-color:var(--custom-button-secondary-bg, #e5e7eb);color:var(--custom-button-secondary-color, #1f2937)}.custom-button--secondary:hover:not(.custom-button--disabled){background-color:var(--custom-button-secondary-hover-bg, #d1d5db)}.custom-button--secondary:active:not(.custom-button--disabled){background-color:var(--custom-button-secondary-active-bg, #9ca3af)}.custom-button--text{background-color:transparent;color:var(--custom-button-text-color, #2563eb)}.custom-button--text:hover:not(.custom-button--disabled){background-color:var(--custom-button-text-hover-bg, rgba(37, 99, 235, 0.08))}.custom-button--text:active:not(.custom-button--disabled){background-color:var(--custom-button-text-active-bg, rgba(37, 99, 235, 0.12))}.custom-button--disabled,.custom-button:disabled{opacity:0.6;cursor:not-allowed}`;
6492
+ const buttonCss = () => `:host{display:inline-block}.fast-button{display:inline-flex;align-items:center;justify-content:center;padding:0.5rem 1rem;font-family:inherit;font-size:0.875rem;font-weight:500;line-height:1.25;border:none;border-radius:0.375rem;cursor:pointer;transition:background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease}.fast-button:focus{outline:2px solid var(--custom-button-focus-ring, #2563eb);outline-offset:2px}.fast-button:focus:not(:focus-visible){outline:none}.fast-button--primary{background-color:var(--custom-button-primary-bg, #2563eb);color:var(--custom-button-primary-color, #fff)}.fast-button--primary:hover:not(.fast-button--disabled){background-color:var(--custom-button-primary-hover-bg, #1d4ed8)}.fast-button--primary:active:not(.fast-button--disabled){background-color:var(--custom-button-primary-active-bg, #1e40af)}.fast-button--secondary{background-color:var(--custom-button-secondary-bg, #e5e7eb);color:var(--custom-button-secondary-color, #1f2937)}.fast-button--secondary:hover:not(.fast-button--disabled){background-color:var(--custom-button-secondary-hover-bg, #d1d5db)}.fast-button--secondary:active:not(.fast-button--disabled){background-color:var(--custom-button-secondary-active-bg, #9ca3af)}.fast-button--text{background-color:transparent;color:var(--custom-button-text-color, #2563eb)}.fast-button--text:hover:not(.fast-button--disabled){background-color:var(--custom-button-text-hover-bg, rgba(37, 99, 235, 0.08))}.fast-button--text:active:not(.fast-button--disabled){background-color:var(--custom-button-text-active-bg, rgba(37, 99, 235, 0.12))}.fast-button--disabled,.fast-button:disabled{opacity:0.6;cursor:not-allowed}`;
6458
6493
 
6459
- class CustomButton {
6494
+ class FastButton {
6460
6495
  constructor(hostRef) {
6461
6496
  registerInstance(this, hostRef);
6462
6497
  this.buttonClick = createEvent(this, "buttonClick");
6463
6498
  }
6464
- /**
6465
- * Visual variant of the button.
6466
- */
6467
6499
  variant = 'primary';
6468
- /**
6469
- * Native button type (button or submit).
6470
- */
6471
- type = 'button';
6472
- /**
6473
- * When true, the button is disabled and does not emit events.
6474
- */
6500
+ type = 'submit';
6475
6501
  disabled = false;
6476
- /**
6477
- * Emitted when the button is clicked (not emitted when disabled).
6478
- */
6479
6502
  buttonClick;
6480
6503
  handleClick = (e) => {
6481
6504
  if (this.disabled) {
@@ -6486,11 +6509,11 @@ class CustomButton {
6486
6509
  this.buttonClick.emit(e);
6487
6510
  };
6488
6511
  render() {
6489
- return (hAsync("button", { key: '3b74909afe4e305dfd38f0b07657202e3d5bfccd', type: this.type, class: {
6490
- 'custom-button': true,
6491
- [`custom-button--${this.variant}`]: true,
6492
- 'custom-button--disabled': this.disabled,
6493
- }, disabled: this.disabled, onClick: this.handleClick }, hAsync("slot", { key: '49845d350e4665c5c66e30bd9262f788eaaa1e20' })));
6512
+ return (hAsync("button", { key: 'b8e811748ade97c941bdf197a311d69d2801a120', type: this.type, class: {
6513
+ 'fast-button': true,
6514
+ [`fast-button--${this.variant}`]: true,
6515
+ 'fast-button--disabled': this.disabled,
6516
+ }, disabled: this.disabled, onClick: this.handleClick }, hAsync("slot", { key: 'a6b987059cc33799abd580ea11f0fe644a3973bf' })));
6494
6517
  }
6495
6518
  static get style() { return buttonCss(); }
6496
6519
  static get cmpMeta() { return {
@@ -6507,6 +6530,213 @@ class CustomButton {
6507
6530
  }; }
6508
6531
  }
6509
6532
 
6533
+ const fastFormCss = () => `.fast-form{display:flex;gap:0.5rem;align-items:flex-start}`;
6534
+
6535
+ class FastForm {
6536
+ constructor(hostRef) {
6537
+ registerInstance(this, hostRef);
6538
+ this.searchExecuted = createEvent(this, "searchExecuted");
6539
+ }
6540
+ get el() { return getElement(this); }
6541
+ searchExecuted;
6542
+ handleInputSubmit() {
6543
+ this.submit();
6544
+ }
6545
+ async submit() {
6546
+ const inputs = this.el.querySelectorAll('fast-input');
6547
+ const params = {};
6548
+ for (const input of Array.from(inputs)) {
6549
+ const paramName = await input.getParamName();
6550
+ const value = await input.getValue();
6551
+ if (value) {
6552
+ params[paramName] = value;
6553
+ }
6554
+ }
6555
+ this.updateUrlParams(params);
6556
+ document.dispatchEvent(new CustomEvent('search-executed', {
6557
+ detail: params,
6558
+ bubbles: true,
6559
+ composed: true,
6560
+ }));
6561
+ this.searchExecuted.emit(params);
6562
+ }
6563
+ updateUrlParams(params) {
6564
+ const urlParams = new URLSearchParams(window.location.search);
6565
+ // Get all param names from inputs and clear them first
6566
+ const inputs = this.el.querySelectorAll('fast-input');
6567
+ for (const input of Array.from(inputs)) {
6568
+ const paramName = input.paramName;
6569
+ if (paramName) {
6570
+ urlParams.delete(paramName);
6571
+ }
6572
+ }
6573
+ // Set new values
6574
+ for (const [key, value] of Object.entries(params)) {
6575
+ if (value) {
6576
+ urlParams.set(key, value);
6577
+ }
6578
+ }
6579
+ const qs = urlParams.toString();
6580
+ const newUrl = qs
6581
+ ? `${window.location.pathname}?${qs}`
6582
+ : window.location.pathname;
6583
+ history.pushState({}, '', newUrl);
6584
+ }
6585
+ handleFormSubmit = (e) => {
6586
+ e.preventDefault();
6587
+ this.submit();
6588
+ };
6589
+ render() {
6590
+ return (hAsync("form", { key: '50436bb904e8b175ae0c23158812a4e4756544c0', class: "fast-form", onSubmit: this.handleFormSubmit }, hAsync("slot", { key: '2f76c4c42c15ebf389d6e0fdbcb9a2098bf1156a' })));
6591
+ }
6592
+ static get style() { return fastFormCss(); }
6593
+ static get cmpMeta() { return {
6594
+ "$flags$": 260,
6595
+ "$tagName$": "fast-form",
6596
+ "$members$": undefined,
6597
+ "$listeners$": [[0, "inputSubmit", "handleInputSubmit"]],
6598
+ "$lazyBundleId$": "-",
6599
+ "$attrsToReflect$": []
6600
+ }; }
6601
+ }
6602
+
6603
+ const fastInputCss = () => `.fast-input{position:relative;display:inline-block}.fast-input__field{padding:0.5rem 0.75rem;font-size:1rem;border:1px solid #ccc;border-radius:4px;width:100%;box-sizing:border-box}.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}`;
6604
+
6605
+ class FastInput {
6606
+ constructor(hostRef) {
6607
+ registerInstance(this, hostRef);
6608
+ this.inputSubmit = createEvent(this, "inputSubmit");
6609
+ this.inputChanged = createEvent(this, "inputChanged");
6610
+ }
6611
+ placeholder = 'Search...';
6612
+ value = '';
6613
+ paramName = 'keyword';
6614
+ enableAutocomplete = false;
6615
+ autocompleteUrl = '/api/jobs/autocomplete';
6616
+ targetPath;
6617
+ debounceMs = 300;
6618
+ minChars = 3;
6619
+ inputSubmit;
6620
+ inputChanged;
6621
+ inputValue = '';
6622
+ suggestions = [];
6623
+ showDropdown = false;
6624
+ autocompleteLoading = false;
6625
+ debounceTimer;
6626
+ popstateHandler;
6627
+ async getValue() {
6628
+ return this.inputValue;
6629
+ }
6630
+ async getParamName() {
6631
+ return this.paramName;
6632
+ }
6633
+ connectedCallback() {
6634
+ const urlValue = this.getUrlParam();
6635
+ this.inputValue = urlValue !== null ? urlValue : this.value;
6636
+ this.popstateHandler = () => {
6637
+ this.inputValue = this.getUrlParam() ?? '';
6638
+ };
6639
+ window.addEventListener('popstate', this.popstateHandler);
6640
+ }
6641
+ disconnectedCallback() {
6642
+ window.removeEventListener('popstate', this.popstateHandler);
6643
+ clearTimeout(this.debounceTimer);
6644
+ }
6645
+ getUrlParam() {
6646
+ const params = new URLSearchParams(window.location.search);
6647
+ return params.get(this.paramName);
6648
+ }
6649
+ handleInput = (e) => {
6650
+ const value = e.target.value;
6651
+ this.inputValue = value;
6652
+ this.inputChanged.emit({ value });
6653
+ if (this.enableAutocomplete) {
6654
+ this.scheduleAutocomplete(value);
6655
+ }
6656
+ };
6657
+ handleKeydown = (e) => {
6658
+ if (e.key === 'Enter') {
6659
+ e.preventDefault();
6660
+ this.showDropdown = false;
6661
+ this.inputSubmit.emit();
6662
+ }
6663
+ else if (e.key === 'Escape') {
6664
+ this.showDropdown = false;
6665
+ }
6666
+ };
6667
+ handleBlur = () => {
6668
+ this.showDropdown = false;
6669
+ };
6670
+ scheduleAutocomplete(value) {
6671
+ clearTimeout(this.debounceTimer);
6672
+ if (value.length < this.minChars) {
6673
+ this.showDropdown = false;
6674
+ return;
6675
+ }
6676
+ this.debounceTimer = setTimeout(() => this.fetchSuggestions(value), this.debounceMs);
6677
+ }
6678
+ async fetchSuggestions(keyword) {
6679
+ if (!this.targetPath) {
6680
+ console.warn('[fast-input] target-path is required for autocomplete');
6681
+ return;
6682
+ }
6683
+ this.autocompleteLoading = true;
6684
+ this.showDropdown = true;
6685
+ try {
6686
+ const res = await fetch(this.autocompleteUrl, {
6687
+ method: 'POST',
6688
+ headers: { 'Content-Type': 'application/json' },
6689
+ body: JSON.stringify({ keyword, target_path: this.targetPath }),
6690
+ });
6691
+ if (!res.ok)
6692
+ throw new Error('autocomplete request failed');
6693
+ const data = await res.json();
6694
+ this.suggestions = data;
6695
+ }
6696
+ catch {
6697
+ this.showDropdown = false;
6698
+ this.suggestions = [];
6699
+ }
6700
+ finally {
6701
+ this.autocompleteLoading = false;
6702
+ }
6703
+ }
6704
+ selectSuggestion(title) {
6705
+ this.inputValue = title;
6706
+ this.showDropdown = false;
6707
+ this.inputSubmit.emit();
6708
+ }
6709
+ render() {
6710
+ return (hAsync("div", { key: 'ea03f3dd68d0da4fc30cbc18a1ef66b5974a4f89', class: "fast-input" }, hAsync("input", { key: '31d85c4c90dde0e10ea9424b960c5e4f4a3ca54a', type: "text", class: "fast-input__field", placeholder: this.placeholder, value: this.inputValue, onInput: this.handleInput, onKeyDown: this.handleKeydown, onBlur: this.handleBlur }), this.enableAutocomplete && this.showDropdown && (hAsync("ul", { key: '0de6fd68709d906bafd2fec684f0a73045be3705', 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 => {
6711
+ e.preventDefault();
6712
+ }, onClick: () => this.selectSuggestion(s.title) }, s.title))))))));
6713
+ }
6714
+ static get style() { return fastInputCss(); }
6715
+ static get cmpMeta() { return {
6716
+ "$flags$": 512,
6717
+ "$tagName$": "fast-input",
6718
+ "$members$": {
6719
+ "placeholder": [1],
6720
+ "value": [1],
6721
+ "paramName": [1, "param-name"],
6722
+ "enableAutocomplete": [4, "enable-autocomplete"],
6723
+ "autocompleteUrl": [1, "autocomplete-url"],
6724
+ "targetPath": [1, "target-path"],
6725
+ "debounceMs": [2, "debounce-ms"],
6726
+ "minChars": [2, "min-chars"],
6727
+ "inputValue": [32],
6728
+ "suggestions": [32],
6729
+ "showDropdown": [32],
6730
+ "autocompleteLoading": [32],
6731
+ "getValue": [64],
6732
+ "getParamName": [64]
6733
+ },
6734
+ "$listeners$": undefined,
6735
+ "$lazyBundleId$": "-",
6736
+ "$attrsToReflect$": []
6737
+ }; }
6738
+ }
6739
+
6510
6740
  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
6741
 
6512
6742
  /**
@@ -6829,6 +7059,7 @@ const defaultNoResultsLine2 = 'Please refine your keywords in the search bar abo
6829
7059
  class JobsListOnly {
6830
7060
  constructor(hostRef) {
6831
7061
  registerInstance(this, hostRef);
7062
+ this.fetchComplete = createEvent(this, "fetchComplete");
6832
7063
  }
6833
7064
  /**
6834
7065
  * When "true", use built-in mock data (for local/dev/docs). Otherwise use `jobs` from API/parent.
@@ -6852,16 +7083,84 @@ class JobsListOnly {
6852
7083
  enableKilometers = false;
6853
7084
  /** Extra CSS class on the root element (avoid prop name "class" / "classname" reserved). */
6854
7085
  rootClass = '';
7086
+ /** Template string for count display. Tokens: {count} = jobs on page, {total} = total from API. */
7087
+ showCountText = '';
6855
7088
  showSuggestions = false;
6856
7089
  clearResultSuggestionsTitleText = 'Suggestions';
6857
7090
  clearResultSuggestionsLine1 = 'Try different keywords';
6858
7091
  clearResultSuggestionsLine2 = 'Make sure everything is spelled correctly';
6859
7092
  clearResultSuggestionsLine3 = 'Try other locations';
6860
7093
  clearResultSuggestionsLine4 = '';
7094
+ /** When true, component manages its own data fetching */
7095
+ autoFetch = false;
7096
+ /** Jobs search endpoint */
7097
+ apiUrl = '/api/get-jobs';
7098
+ /** Comma-separated URL param names to watch and forward to the API */
7099
+ watchParams = 'keyword';
7100
+ fetchedJobs = [];
7101
+ fetchedTotal = 0;
7102
+ fetchLoading = false;
7103
+ fetchComplete;
7104
+ searchExecutedHandler;
7105
+ popstateHandler;
7106
+ connectedCallback() {
7107
+ if (this.autoFetch) {
7108
+ this.fetchJobs();
7109
+ this.searchExecutedHandler = () => this.fetchJobs();
7110
+ this.popstateHandler = () => this.fetchJobs();
7111
+ document.addEventListener('search-executed', this.searchExecutedHandler);
7112
+ window.addEventListener('popstate', this.popstateHandler);
7113
+ }
7114
+ }
7115
+ disconnectedCallback() {
7116
+ if (this.autoFetch) {
7117
+ document.removeEventListener('search-executed', this.searchExecutedHandler);
7118
+ window.removeEventListener('popstate', this.popstateHandler);
7119
+ }
7120
+ }
7121
+ async fetchJobs() {
7122
+ this.fetchLoading = true;
7123
+ const params = new URLSearchParams(window.location.search);
7124
+ const watchList = this.watchParams.split(',').map(p => p.trim()).filter(Boolean);
7125
+ const query = new URLSearchParams();
7126
+ for (const key of watchList) {
7127
+ const val = params.get(key);
7128
+ if (val !== null)
7129
+ query.set(key, val);
7130
+ }
7131
+ const url = `${this.apiUrl}?${query.toString()}`;
7132
+ try {
7133
+ const res = await fetch(url, {
7134
+ method: 'POST',
7135
+ headers: { 'Content-Type': 'application/json' },
7136
+ body: JSON.stringify({ disable_switch_search_mode: false }),
7137
+ });
7138
+ if (!res.ok)
7139
+ throw new Error('fetch failed');
7140
+ const data = await res.json();
7141
+ this.fetchedJobs = data.jobs;
7142
+ this.fetchedTotal = data.totalJob;
7143
+ this.fetchComplete.emit({ jobs: data.jobs, totalJob: data.totalJob });
7144
+ }
7145
+ catch {
7146
+ // preserve stale data, just stop loading
7147
+ }
7148
+ finally {
7149
+ this.fetchLoading = false;
7150
+ }
7151
+ }
7152
+ renderCountText(count, total) {
7153
+ return this.showCountText
7154
+ .replace('{count}', String(count))
7155
+ .replace('{total}', String(total));
7156
+ }
6861
7157
  getJobsArray() {
6862
7158
  if (this.mockData) {
6863
7159
  return mockJobsListOnly;
6864
7160
  }
7161
+ if (this.autoFetch) {
7162
+ return this.fetchedJobs;
7163
+ }
6865
7164
  const j = this.jobs;
6866
7165
  if (Array.isArray(j))
6867
7166
  return j;
@@ -6881,11 +7180,15 @@ class JobsListOnly {
6881
7180
  }
6882
7181
  render() {
6883
7182
  const jobsArray = this.getJobsArray();
6884
- const loading = this.mockData ? false : this.loading;
6885
- const totalJob = this.mockData ? jobsArray.length : (this.totalJob || jobsArray.length);
7183
+ const loading = this.mockData ? false : (this.autoFetch ? this.fetchLoading : this.loading);
7184
+ const totalJob = this.mockData
7185
+ ? jobsArray.length
7186
+ : this.autoFetch
7187
+ ? this.fetchedTotal
7188
+ : (this.totalJob || jobsArray.length);
6886
7189
  const showNoResults = !loading && totalJob === 0 && !this.showSuggestions;
6887
7190
  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))))))));
7191
+ return (hAsync("div", { key: '1974ecb7e1ded8237d851560fc4b20dd63b4e941', class: `jobs-list-root ${this.rootClass}`.trim() }, hAsync("div", { key: '3d80283e8508cbe9ec4aa4516a6f832479374e08', class: "results-container" }, this.autoFetch && this.fetchLoading && (hAsync("div", { key: 'c5d3c5362a10ce2442925093118d3436227e8058', class: "jobs-list-only__loading" }, "Loading...")), hAsync("div", { key: 'c68e5aebee17cce16947029031b63364ab25ecda', class: loading ? 'loader' : 'loader hide', "aria-hidden": !loading }), totalJob > 0 && this.showCountText && (hAsync("p", { key: 'e353fa146040fed1aed050ed3ed833903efcdf41', class: "jobs-list-only__count" }, this.renderCountText(jobsArray.length, totalJob))), totalJob > 0 && (hAsync("div", { key: 'e38346f641a1e776a7e70525bf8f3a6a38b6eea5', class: "card" }, hAsync("ul", { key: 'db82d99df76e33ad6041d4ec8dd7908cdf8b89d7', class: "results-list front" }, jobsArray.map((job, index) => this.renderJobItem(job, index))))), showNoResults && (hAsync("div", { key: '8bd8f45ddb9fcd534c7f386919c1e6cd7a4fb6f3', class: "share-jobs__no-results" }, hAsync("h2", { key: '228d32e9dd38f61bcec9f522d3bc4207e17b5365' }, this.noResultsLine1), hAsync("h3", { key: '4289b10974936669a00afba6f1d55e33874198b1' }, this.noResultsLine2))), showSuggestionsBlock && (hAsync("div", { key: '949e1fe01b2db39a69729a9fbc2eebaf2ea461ba', class: "card primary-color" }, hAsync("h4", { key: 'af3361f98987a3a19d45afc1174a99fc4174a65a', class: "result-suggestions-title" }, this.clearResultSuggestionsTitleText, ":"), hAsync("ul", { key: 'd7b9236dbbeb9f9596d642aa588d1c1305ed658b', class: "results-list front" }, hAsync("li", { key: '0e65c9b985e5708096c8a4b0ea1455b6696db6dd', class: "result-suggestions-line" }, this.clearResultSuggestionsLine1), hAsync("li", { key: 'a7fe8459960af60acc81822b48436a32c0e4ad0c', class: "result-suggestions-line" }, this.clearResultSuggestionsLine2), hAsync("li", { key: '1e41d5730de755d6b2cb9e5fbda33704816f9096', class: "result-suggestions-line" }, this.clearResultSuggestionsLine3), this.clearResultSuggestionsLine4 && (hAsync("li", { key: '9ab1bee87135bfda9996b2c99134597475bfaecb', class: "result-suggestions-line" }, this.clearResultSuggestionsLine4))))))));
6889
7192
  }
6890
7193
  static get style() { return jobsListOnlyCss(); }
6891
7194
  static get cmpMeta() { return {
@@ -6907,12 +7210,136 @@ class JobsListOnly {
6907
7210
  "remoteLocationText": [1, "remote-location-text"],
6908
7211
  "enableKilometers": [4, "enable-kilometers"],
6909
7212
  "rootClass": [1, "root-class"],
7213
+ "showCountText": [1, "show-count-text"],
6910
7214
  "showSuggestions": [4, "show-suggestions"],
6911
7215
  "clearResultSuggestionsTitleText": [1, "clear-result-suggestions-title-text"],
6912
7216
  "clearResultSuggestionsLine1": [1, "clear-result-suggestions-line-1"],
6913
7217
  "clearResultSuggestionsLine2": [1, "clear-result-suggestions-line-2"],
6914
7218
  "clearResultSuggestionsLine3": [1, "clear-result-suggestions-line-3"],
6915
- "clearResultSuggestionsLine4": [1, "clear-result-suggestions-line-4"]
7219
+ "clearResultSuggestionsLine4": [1, "clear-result-suggestions-line-4"],
7220
+ "autoFetch": [4, "auto-fetch"],
7221
+ "apiUrl": [1, "api-url"],
7222
+ "watchParams": [1, "watch-params"],
7223
+ "fetchedJobs": [32],
7224
+ "fetchedTotal": [32],
7225
+ "fetchLoading": [32]
7226
+ },
7227
+ "$listeners$": undefined,
7228
+ "$lazyBundleId$": "-",
7229
+ "$attrsToReflect$": []
7230
+ }; }
7231
+ }
7232
+
7233
+ const jobsListReactiveCss = () => `jobs-list-reactive{display:block}jobs-list-reactive.loading{opacity:0.6;pointer-events:none}`;
7234
+
7235
+ class JobsListReactive {
7236
+ constructor(hostRef) {
7237
+ registerInstance(this, hostRef);
7238
+ this.fetchComplete = createEvent(this, "fetchComplete");
7239
+ }
7240
+ get el() { return getElement(this); }
7241
+ /** Jobs search endpoint */
7242
+ apiUrl = '/api/get-jobs';
7243
+ /** Comma-separated URL param names to watch and forward to the API */
7244
+ watchParams = 'keyword,location_name';
7245
+ /** CSS class added to container while fetching */
7246
+ loadingClass = 'loading';
7247
+ isLoading = false;
7248
+ fetchComplete;
7249
+ templateEl = null;
7250
+ searchExecutedHandler;
7251
+ popstateHandler;
7252
+ connectedCallback() {
7253
+ this.templateEl = this.el.querySelector('template');
7254
+ this.searchExecutedHandler = () => this.fetchJobs();
7255
+ this.popstateHandler = () => this.fetchJobs();
7256
+ document.addEventListener('search-executed', this.searchExecutedHandler);
7257
+ window.addEventListener('popstate', this.popstateHandler);
7258
+ }
7259
+ disconnectedCallback() {
7260
+ document.removeEventListener('search-executed', this.searchExecutedHandler);
7261
+ window.removeEventListener('popstate', this.popstateHandler);
7262
+ }
7263
+ buildQueryString() {
7264
+ const urlParams = new URLSearchParams(window.location.search);
7265
+ const watchList = this.watchParams.split(',').map(p => p.trim()).filter(Boolean);
7266
+ const query = new URLSearchParams();
7267
+ for (const key of watchList) {
7268
+ const val = urlParams.get(key);
7269
+ if (val !== null && val !== '') {
7270
+ query.set(key, val);
7271
+ }
7272
+ }
7273
+ return query.toString();
7274
+ }
7275
+ async fetchJobs() {
7276
+ this.isLoading = true;
7277
+ this.el.classList.add(this.loadingClass);
7278
+ const queryString = this.buildQueryString();
7279
+ const url = queryString ? `${this.apiUrl}?${queryString}` : this.apiUrl;
7280
+ try {
7281
+ const res = await fetch(url, {
7282
+ method: 'POST',
7283
+ headers: { 'Content-Type': 'application/json' },
7284
+ body: JSON.stringify({ disable_switch_search_mode: false }),
7285
+ });
7286
+ if (!res.ok)
7287
+ throw new Error('fetch failed');
7288
+ const data = await res.json();
7289
+ this.renderJobs(data.jobs);
7290
+ this.updateCountElements(data.jobs.length, data.totalJob);
7291
+ this.fetchComplete.emit({ jobs: data.jobs, totalJob: data.totalJob });
7292
+ }
7293
+ catch {
7294
+ // Preserve stale data on error
7295
+ }
7296
+ finally {
7297
+ this.isLoading = false;
7298
+ this.el.classList.remove(this.loadingClass);
7299
+ }
7300
+ }
7301
+ renderJobs(jobs) {
7302
+ if (!this.templateEl)
7303
+ return;
7304
+ // Remove all children except the template
7305
+ const children = Array.from(this.el.children);
7306
+ for (const child of children) {
7307
+ if (child !== this.templateEl) {
7308
+ child.remove();
7309
+ }
7310
+ }
7311
+ // Clone template and render each job
7312
+ for (const job of jobs) {
7313
+ const clone = this.templateEl.content.cloneNode(true);
7314
+ const jobCard = clone.querySelector('job-card');
7315
+ if (jobCard) {
7316
+ jobCard.setAttribute('job', JSON.stringify(job));
7317
+ }
7318
+ this.el.appendChild(clone);
7319
+ }
7320
+ }
7321
+ updateCountElements(count, total) {
7322
+ const countEls = document.querySelectorAll('[data-job-count]');
7323
+ const totalEls = document.querySelectorAll('[data-job-total]');
7324
+ countEls.forEach(el => {
7325
+ el.textContent = String(count);
7326
+ });
7327
+ totalEls.forEach(el => {
7328
+ el.textContent = String(total);
7329
+ });
7330
+ }
7331
+ render() {
7332
+ return hAsync("slot", { key: '9f3ab802e19a298a790cfb5e86a4f4888e466804' });
7333
+ }
7334
+ static get style() { return jobsListReactiveCss(); }
7335
+ static get cmpMeta() { return {
7336
+ "$flags$": 772,
7337
+ "$tagName$": "jobs-list-reactive",
7338
+ "$members$": {
7339
+ "apiUrl": [1, "api-url"],
7340
+ "watchParams": [1, "watch-params"],
7341
+ "loadingClass": [1, "loading-class"],
7342
+ "isLoading": [32]
6916
7343
  },
6917
7344
  "$listeners$": undefined,
6918
7345
  "$lazyBundleId$": "-",
@@ -6922,10 +7349,13 @@ class JobsListOnly {
6922
7349
 
6923
7350
  registerComponents([
6924
7351
  AppCarousel,
6925
- CustomButton,
7352
+ FastButton,
7353
+ FastForm,
7354
+ FastInput,
6926
7355
  JobCard,
6927
7356
  JobsItem,
6928
7357
  JobsListOnly,
7358
+ JobsListReactive,
6929
7359
  ]);
6930
7360
 
6931
7361
  exports.hydrateApp = hydrateApp;