@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.mjs CHANGED
@@ -130,7 +130,7 @@ function hydrateFactory($stencilWindow, $stencilHydrateOpts, $stencilHydrateResu
130
130
 
131
131
 
132
132
  const NAMESPACE = 'web-component-poc';
133
- const BUILD = /* web-component-poc */ { hotModuleReplacement: false, hydratedSelectorName: "hydrated", prop: true, propChangeCallback: false, shadowDom: false, slotRelocation: true, updatable: true};
133
+ const BUILD = /* web-component-poc */ { hotModuleReplacement: false, hydratedSelectorName: "hydrated", propChangeCallback: false, shadowDom: false, slotRelocation: true, state: true, updatable: true};
134
134
 
135
135
  /*
136
136
  Stencil Hydrate Platform v4.43.2 | MIT Licensed | https://stenciljs.com
@@ -2097,6 +2097,13 @@ var dispatchHooks = (hostRef, isInitialLoad) => {
2097
2097
  hostRef.$deferredConnectedCallback$ = false;
2098
2098
  safeCall(instance, "connectedCallback", void 0, elm);
2099
2099
  }
2100
+ {
2101
+ hostRef.$flags$ |= 256 /* isListenReady */;
2102
+ if (hostRef.$queuedListeners$) {
2103
+ hostRef.$queuedListeners$.map(([methodName, event]) => safeCall(instance, methodName, event, elm));
2104
+ hostRef.$queuedListeners$ = void 0;
2105
+ }
2106
+ }
2100
2107
  if (hostRef.$fetchedCbList$.length) {
2101
2108
  hostRef.$fetchedCbList$.forEach((cb) => cb(elm));
2102
2109
  }
@@ -2476,6 +2483,7 @@ var connectedCallback = (elm) => {
2476
2483
  initializeComponent(elm, hostRef, cmpMeta);
2477
2484
  }
2478
2485
  } else {
2486
+ addHostEventListeners(elm, hostRef, cmpMeta.$listeners$);
2479
2487
  if (hostRef == null ? void 0 : hostRef.$lazyInstance$) {
2480
2488
  fireConnectedCallback(hostRef.$lazyInstance$, elm);
2481
2489
  } else if (hostRef == null ? void 0 : hostRef.$onReadyPromise$) {
@@ -2495,6 +2503,32 @@ var setContentReference = (elm) => {
2495
2503
  contentRefElm["s-cn"] = true;
2496
2504
  insertBefore(elm, contentRefElm, elm.firstChild);
2497
2505
  };
2506
+ var addHostEventListeners = (elm, hostRef, listeners, attachParentListeners) => {
2507
+ if (listeners && win.document) {
2508
+ listeners.map(([flags, name, method]) => {
2509
+ const target = elm;
2510
+ const handler = hostListenerProxy(hostRef, method);
2511
+ const opts = hostListenerOpts(flags);
2512
+ plt.ael(target, name, handler, opts);
2513
+ (hostRef.$rmListeners$ = hostRef.$rmListeners$ || []).push(() => plt.rel(target, name, handler, opts));
2514
+ });
2515
+ }
2516
+ };
2517
+ var hostListenerProxy = (hostRef, methodName) => (ev) => {
2518
+ var _a;
2519
+ try {
2520
+ {
2521
+ if (hostRef.$flags$ & 256 /* isListenReady */) {
2522
+ (_a = hostRef.$lazyInstance$) == null ? void 0 : _a[methodName](ev);
2523
+ } else {
2524
+ (hostRef.$queuedListeners$ = hostRef.$queuedListeners$ || []).push([methodName, ev]);
2525
+ }
2526
+ }
2527
+ } catch (e) {
2528
+ consoleError(e, hostRef.$hostElement$);
2529
+ }
2530
+ };
2531
+ var hostListenerOpts = (flags) => (flags & 2 /* Capture */) !== 0;
2498
2532
  function transformTag(tag) {
2499
2533
  return tag;
2500
2534
  }
@@ -4275,6 +4309,7 @@ async function hydrateComponent(win2, results, tagName, elm, waitingElements) {
4275
4309
  if (!hostRef) {
4276
4310
  return;
4277
4311
  }
4312
+ addHostEventListeners(this, hostRef, cmpMeta.$listeners$);
4278
4313
  try {
4279
4314
  connectedCallback(elm);
4280
4315
  await elm.componentOnReady();
@@ -4505,7 +4540,7 @@ var registerInstance = (lazyInstance, hostRef) => {
4505
4540
  if (!hostRef) return void 0;
4506
4541
  lazyInstance.__stencil__getHostRef = () => hostRef;
4507
4542
  hostRef.$lazyInstance$ = lazyInstance;
4508
- if (hostRef.$cmpMeta$.$flags$ & 512 /* hasModernPropertyDecls */ && (BUILD.prop)) {
4543
+ if (hostRef.$cmpMeta$.$flags$ & 512 /* hasModernPropertyDecls */ && (BUILD.state)) {
4509
4544
  reWireGetterSetter(lazyInstance, hostRef);
4510
4545
  }
4511
4546
  return hostRef;
@@ -6452,28 +6487,16 @@ class AppCarousel {
6452
6487
  }; }
6453
6488
  }
6454
6489
 
6455
- 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}`;
6490
+ 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}`;
6456
6491
 
6457
- class CustomButton {
6492
+ class FastButton {
6458
6493
  constructor(hostRef) {
6459
6494
  registerInstance(this, hostRef);
6460
6495
  this.buttonClick = createEvent(this, "buttonClick");
6461
6496
  }
6462
- /**
6463
- * Visual variant of the button.
6464
- */
6465
6497
  variant = 'primary';
6466
- /**
6467
- * Native button type (button or submit).
6468
- */
6469
- type = 'button';
6470
- /**
6471
- * When true, the button is disabled and does not emit events.
6472
- */
6498
+ type = 'submit';
6473
6499
  disabled = false;
6474
- /**
6475
- * Emitted when the button is clicked (not emitted when disabled).
6476
- */
6477
6500
  buttonClick;
6478
6501
  handleClick = (e) => {
6479
6502
  if (this.disabled) {
@@ -6484,11 +6507,11 @@ class CustomButton {
6484
6507
  this.buttonClick.emit(e);
6485
6508
  };
6486
6509
  render() {
6487
- return (hAsync("button", { key: '3b74909afe4e305dfd38f0b07657202e3d5bfccd', type: this.type, class: {
6488
- 'custom-button': true,
6489
- [`custom-button--${this.variant}`]: true,
6490
- 'custom-button--disabled': this.disabled,
6491
- }, disabled: this.disabled, onClick: this.handleClick }, hAsync("slot", { key: '49845d350e4665c5c66e30bd9262f788eaaa1e20' })));
6510
+ return (hAsync("button", { key: 'b8e811748ade97c941bdf197a311d69d2801a120', type: this.type, class: {
6511
+ 'fast-button': true,
6512
+ [`fast-button--${this.variant}`]: true,
6513
+ 'fast-button--disabled': this.disabled,
6514
+ }, disabled: this.disabled, onClick: this.handleClick }, hAsync("slot", { key: 'a6b987059cc33799abd580ea11f0fe644a3973bf' })));
6492
6515
  }
6493
6516
  static get style() { return buttonCss(); }
6494
6517
  static get cmpMeta() { return {
@@ -6505,6 +6528,213 @@ class CustomButton {
6505
6528
  }; }
6506
6529
  }
6507
6530
 
6531
+ const fastFormCss = () => `.fast-form{display:flex;gap:0.5rem;align-items:flex-start}`;
6532
+
6533
+ class FastForm {
6534
+ constructor(hostRef) {
6535
+ registerInstance(this, hostRef);
6536
+ this.searchExecuted = createEvent(this, "searchExecuted");
6537
+ }
6538
+ get el() { return getElement(this); }
6539
+ searchExecuted;
6540
+ handleInputSubmit() {
6541
+ this.submit();
6542
+ }
6543
+ async submit() {
6544
+ const inputs = this.el.querySelectorAll('fast-input');
6545
+ const params = {};
6546
+ for (const input of Array.from(inputs)) {
6547
+ const paramName = await input.getParamName();
6548
+ const value = await input.getValue();
6549
+ if (value) {
6550
+ params[paramName] = value;
6551
+ }
6552
+ }
6553
+ this.updateUrlParams(params);
6554
+ document.dispatchEvent(new CustomEvent('search-executed', {
6555
+ detail: params,
6556
+ bubbles: true,
6557
+ composed: true,
6558
+ }));
6559
+ this.searchExecuted.emit(params);
6560
+ }
6561
+ updateUrlParams(params) {
6562
+ const urlParams = new URLSearchParams(window.location.search);
6563
+ // Get all param names from inputs and clear them first
6564
+ const inputs = this.el.querySelectorAll('fast-input');
6565
+ for (const input of Array.from(inputs)) {
6566
+ const paramName = input.paramName;
6567
+ if (paramName) {
6568
+ urlParams.delete(paramName);
6569
+ }
6570
+ }
6571
+ // Set new values
6572
+ for (const [key, value] of Object.entries(params)) {
6573
+ if (value) {
6574
+ urlParams.set(key, value);
6575
+ }
6576
+ }
6577
+ const qs = urlParams.toString();
6578
+ const newUrl = qs
6579
+ ? `${window.location.pathname}?${qs}`
6580
+ : window.location.pathname;
6581
+ history.pushState({}, '', newUrl);
6582
+ }
6583
+ handleFormSubmit = (e) => {
6584
+ e.preventDefault();
6585
+ this.submit();
6586
+ };
6587
+ render() {
6588
+ return (hAsync("form", { key: '50436bb904e8b175ae0c23158812a4e4756544c0', class: "fast-form", onSubmit: this.handleFormSubmit }, hAsync("slot", { key: '2f76c4c42c15ebf389d6e0fdbcb9a2098bf1156a' })));
6589
+ }
6590
+ static get style() { return fastFormCss(); }
6591
+ static get cmpMeta() { return {
6592
+ "$flags$": 260,
6593
+ "$tagName$": "fast-form",
6594
+ "$members$": undefined,
6595
+ "$listeners$": [[0, "inputSubmit", "handleInputSubmit"]],
6596
+ "$lazyBundleId$": "-",
6597
+ "$attrsToReflect$": []
6598
+ }; }
6599
+ }
6600
+
6601
+ 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}`;
6602
+
6603
+ class FastInput {
6604
+ constructor(hostRef) {
6605
+ registerInstance(this, hostRef);
6606
+ this.inputSubmit = createEvent(this, "inputSubmit");
6607
+ this.inputChanged = createEvent(this, "inputChanged");
6608
+ }
6609
+ placeholder = 'Search...';
6610
+ value = '';
6611
+ paramName = 'keyword';
6612
+ enableAutocomplete = false;
6613
+ autocompleteUrl = '/api/jobs/autocomplete';
6614
+ targetPath;
6615
+ debounceMs = 300;
6616
+ minChars = 3;
6617
+ inputSubmit;
6618
+ inputChanged;
6619
+ inputValue = '';
6620
+ suggestions = [];
6621
+ showDropdown = false;
6622
+ autocompleteLoading = false;
6623
+ debounceTimer;
6624
+ popstateHandler;
6625
+ async getValue() {
6626
+ return this.inputValue;
6627
+ }
6628
+ async getParamName() {
6629
+ return this.paramName;
6630
+ }
6631
+ connectedCallback() {
6632
+ const urlValue = this.getUrlParam();
6633
+ this.inputValue = urlValue !== null ? urlValue : this.value;
6634
+ this.popstateHandler = () => {
6635
+ this.inputValue = this.getUrlParam() ?? '';
6636
+ };
6637
+ window.addEventListener('popstate', this.popstateHandler);
6638
+ }
6639
+ disconnectedCallback() {
6640
+ window.removeEventListener('popstate', this.popstateHandler);
6641
+ clearTimeout(this.debounceTimer);
6642
+ }
6643
+ getUrlParam() {
6644
+ const params = new URLSearchParams(window.location.search);
6645
+ return params.get(this.paramName);
6646
+ }
6647
+ handleInput = (e) => {
6648
+ const value = e.target.value;
6649
+ this.inputValue = value;
6650
+ this.inputChanged.emit({ value });
6651
+ if (this.enableAutocomplete) {
6652
+ this.scheduleAutocomplete(value);
6653
+ }
6654
+ };
6655
+ handleKeydown = (e) => {
6656
+ if (e.key === 'Enter') {
6657
+ e.preventDefault();
6658
+ this.showDropdown = false;
6659
+ this.inputSubmit.emit();
6660
+ }
6661
+ else if (e.key === 'Escape') {
6662
+ this.showDropdown = false;
6663
+ }
6664
+ };
6665
+ handleBlur = () => {
6666
+ this.showDropdown = false;
6667
+ };
6668
+ scheduleAutocomplete(value) {
6669
+ clearTimeout(this.debounceTimer);
6670
+ if (value.length < this.minChars) {
6671
+ this.showDropdown = false;
6672
+ return;
6673
+ }
6674
+ this.debounceTimer = setTimeout(() => this.fetchSuggestions(value), this.debounceMs);
6675
+ }
6676
+ async fetchSuggestions(keyword) {
6677
+ if (!this.targetPath) {
6678
+ console.warn('[fast-input] target-path is required for autocomplete');
6679
+ return;
6680
+ }
6681
+ this.autocompleteLoading = true;
6682
+ this.showDropdown = true;
6683
+ try {
6684
+ const res = await fetch(this.autocompleteUrl, {
6685
+ method: 'POST',
6686
+ headers: { 'Content-Type': 'application/json' },
6687
+ body: JSON.stringify({ keyword, target_path: this.targetPath }),
6688
+ });
6689
+ if (!res.ok)
6690
+ throw new Error('autocomplete request failed');
6691
+ const data = await res.json();
6692
+ this.suggestions = data;
6693
+ }
6694
+ catch {
6695
+ this.showDropdown = false;
6696
+ this.suggestions = [];
6697
+ }
6698
+ finally {
6699
+ this.autocompleteLoading = false;
6700
+ }
6701
+ }
6702
+ selectSuggestion(title) {
6703
+ this.inputValue = title;
6704
+ this.showDropdown = false;
6705
+ this.inputSubmit.emit();
6706
+ }
6707
+ render() {
6708
+ 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 => {
6709
+ e.preventDefault();
6710
+ }, onClick: () => this.selectSuggestion(s.title) }, s.title))))))));
6711
+ }
6712
+ static get style() { return fastInputCss(); }
6713
+ static get cmpMeta() { return {
6714
+ "$flags$": 512,
6715
+ "$tagName$": "fast-input",
6716
+ "$members$": {
6717
+ "placeholder": [1],
6718
+ "value": [1],
6719
+ "paramName": [1, "param-name"],
6720
+ "enableAutocomplete": [4, "enable-autocomplete"],
6721
+ "autocompleteUrl": [1, "autocomplete-url"],
6722
+ "targetPath": [1, "target-path"],
6723
+ "debounceMs": [2, "debounce-ms"],
6724
+ "minChars": [2, "min-chars"],
6725
+ "inputValue": [32],
6726
+ "suggestions": [32],
6727
+ "showDropdown": [32],
6728
+ "autocompleteLoading": [32],
6729
+ "getValue": [64],
6730
+ "getParamName": [64]
6731
+ },
6732
+ "$listeners$": undefined,
6733
+ "$lazyBundleId$": "-",
6734
+ "$attrsToReflect$": []
6735
+ }; }
6736
+ }
6737
+
6508
6738
  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}}`;
6509
6739
 
6510
6740
  /**
@@ -6827,6 +7057,7 @@ const defaultNoResultsLine2 = 'Please refine your keywords in the search bar abo
6827
7057
  class JobsListOnly {
6828
7058
  constructor(hostRef) {
6829
7059
  registerInstance(this, hostRef);
7060
+ this.fetchComplete = createEvent(this, "fetchComplete");
6830
7061
  }
6831
7062
  /**
6832
7063
  * When "true", use built-in mock data (for local/dev/docs). Otherwise use `jobs` from API/parent.
@@ -6850,16 +7081,84 @@ class JobsListOnly {
6850
7081
  enableKilometers = false;
6851
7082
  /** Extra CSS class on the root element (avoid prop name "class" / "classname" reserved). */
6852
7083
  rootClass = '';
7084
+ /** Template string for count display. Tokens: {count} = jobs on page, {total} = total from API. */
7085
+ showCountText = '';
6853
7086
  showSuggestions = false;
6854
7087
  clearResultSuggestionsTitleText = 'Suggestions';
6855
7088
  clearResultSuggestionsLine1 = 'Try different keywords';
6856
7089
  clearResultSuggestionsLine2 = 'Make sure everything is spelled correctly';
6857
7090
  clearResultSuggestionsLine3 = 'Try other locations';
6858
7091
  clearResultSuggestionsLine4 = '';
7092
+ /** When true, component manages its own data fetching */
7093
+ autoFetch = false;
7094
+ /** Jobs search endpoint */
7095
+ apiUrl = '/api/get-jobs';
7096
+ /** Comma-separated URL param names to watch and forward to the API */
7097
+ watchParams = 'keyword';
7098
+ fetchedJobs = [];
7099
+ fetchedTotal = 0;
7100
+ fetchLoading = false;
7101
+ fetchComplete;
7102
+ searchExecutedHandler;
7103
+ popstateHandler;
7104
+ connectedCallback() {
7105
+ if (this.autoFetch) {
7106
+ this.fetchJobs();
7107
+ this.searchExecutedHandler = () => this.fetchJobs();
7108
+ this.popstateHandler = () => this.fetchJobs();
7109
+ document.addEventListener('search-executed', this.searchExecutedHandler);
7110
+ window.addEventListener('popstate', this.popstateHandler);
7111
+ }
7112
+ }
7113
+ disconnectedCallback() {
7114
+ if (this.autoFetch) {
7115
+ document.removeEventListener('search-executed', this.searchExecutedHandler);
7116
+ window.removeEventListener('popstate', this.popstateHandler);
7117
+ }
7118
+ }
7119
+ async fetchJobs() {
7120
+ this.fetchLoading = true;
7121
+ const params = new URLSearchParams(window.location.search);
7122
+ const watchList = this.watchParams.split(',').map(p => p.trim()).filter(Boolean);
7123
+ const query = new URLSearchParams();
7124
+ for (const key of watchList) {
7125
+ const val = params.get(key);
7126
+ if (val !== null)
7127
+ query.set(key, val);
7128
+ }
7129
+ const url = `${this.apiUrl}?${query.toString()}`;
7130
+ try {
7131
+ const res = await fetch(url, {
7132
+ method: 'POST',
7133
+ headers: { 'Content-Type': 'application/json' },
7134
+ body: JSON.stringify({ disable_switch_search_mode: false }),
7135
+ });
7136
+ if (!res.ok)
7137
+ throw new Error('fetch failed');
7138
+ const data = await res.json();
7139
+ this.fetchedJobs = data.jobs;
7140
+ this.fetchedTotal = data.totalJob;
7141
+ this.fetchComplete.emit({ jobs: data.jobs, totalJob: data.totalJob });
7142
+ }
7143
+ catch {
7144
+ // preserve stale data, just stop loading
7145
+ }
7146
+ finally {
7147
+ this.fetchLoading = false;
7148
+ }
7149
+ }
7150
+ renderCountText(count, total) {
7151
+ return this.showCountText
7152
+ .replace('{count}', String(count))
7153
+ .replace('{total}', String(total));
7154
+ }
6859
7155
  getJobsArray() {
6860
7156
  if (this.mockData) {
6861
7157
  return mockJobsListOnly;
6862
7158
  }
7159
+ if (this.autoFetch) {
7160
+ return this.fetchedJobs;
7161
+ }
6863
7162
  const j = this.jobs;
6864
7163
  if (Array.isArray(j))
6865
7164
  return j;
@@ -6879,11 +7178,15 @@ class JobsListOnly {
6879
7178
  }
6880
7179
  render() {
6881
7180
  const jobsArray = this.getJobsArray();
6882
- const loading = this.mockData ? false : this.loading;
6883
- const totalJob = this.mockData ? jobsArray.length : (this.totalJob || jobsArray.length);
7181
+ const loading = this.mockData ? false : (this.autoFetch ? this.fetchLoading : this.loading);
7182
+ const totalJob = this.mockData
7183
+ ? jobsArray.length
7184
+ : this.autoFetch
7185
+ ? this.fetchedTotal
7186
+ : (this.totalJob || jobsArray.length);
6884
7187
  const showNoResults = !loading && totalJob === 0 && !this.showSuggestions;
6885
7188
  const showSuggestionsBlock = !loading && totalJob === 0 && this.showSuggestions;
6886
- 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))))))));
7189
+ 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))))))));
6887
7190
  }
6888
7191
  static get style() { return jobsListOnlyCss(); }
6889
7192
  static get cmpMeta() { return {
@@ -6905,12 +7208,136 @@ class JobsListOnly {
6905
7208
  "remoteLocationText": [1, "remote-location-text"],
6906
7209
  "enableKilometers": [4, "enable-kilometers"],
6907
7210
  "rootClass": [1, "root-class"],
7211
+ "showCountText": [1, "show-count-text"],
6908
7212
  "showSuggestions": [4, "show-suggestions"],
6909
7213
  "clearResultSuggestionsTitleText": [1, "clear-result-suggestions-title-text"],
6910
7214
  "clearResultSuggestionsLine1": [1, "clear-result-suggestions-line-1"],
6911
7215
  "clearResultSuggestionsLine2": [1, "clear-result-suggestions-line-2"],
6912
7216
  "clearResultSuggestionsLine3": [1, "clear-result-suggestions-line-3"],
6913
- "clearResultSuggestionsLine4": [1, "clear-result-suggestions-line-4"]
7217
+ "clearResultSuggestionsLine4": [1, "clear-result-suggestions-line-4"],
7218
+ "autoFetch": [4, "auto-fetch"],
7219
+ "apiUrl": [1, "api-url"],
7220
+ "watchParams": [1, "watch-params"],
7221
+ "fetchedJobs": [32],
7222
+ "fetchedTotal": [32],
7223
+ "fetchLoading": [32]
7224
+ },
7225
+ "$listeners$": undefined,
7226
+ "$lazyBundleId$": "-",
7227
+ "$attrsToReflect$": []
7228
+ }; }
7229
+ }
7230
+
7231
+ const jobsListReactiveCss = () => `jobs-list-reactive{display:block}jobs-list-reactive.loading{opacity:0.6;pointer-events:none}`;
7232
+
7233
+ class JobsListReactive {
7234
+ constructor(hostRef) {
7235
+ registerInstance(this, hostRef);
7236
+ this.fetchComplete = createEvent(this, "fetchComplete");
7237
+ }
7238
+ get el() { return getElement(this); }
7239
+ /** Jobs search endpoint */
7240
+ apiUrl = '/api/get-jobs';
7241
+ /** Comma-separated URL param names to watch and forward to the API */
7242
+ watchParams = 'keyword,location_name';
7243
+ /** CSS class added to container while fetching */
7244
+ loadingClass = 'loading';
7245
+ isLoading = false;
7246
+ fetchComplete;
7247
+ templateEl = null;
7248
+ searchExecutedHandler;
7249
+ popstateHandler;
7250
+ connectedCallback() {
7251
+ this.templateEl = this.el.querySelector('template');
7252
+ this.searchExecutedHandler = () => this.fetchJobs();
7253
+ this.popstateHandler = () => this.fetchJobs();
7254
+ document.addEventListener('search-executed', this.searchExecutedHandler);
7255
+ window.addEventListener('popstate', this.popstateHandler);
7256
+ }
7257
+ disconnectedCallback() {
7258
+ document.removeEventListener('search-executed', this.searchExecutedHandler);
7259
+ window.removeEventListener('popstate', this.popstateHandler);
7260
+ }
7261
+ buildQueryString() {
7262
+ const urlParams = new URLSearchParams(window.location.search);
7263
+ const watchList = this.watchParams.split(',').map(p => p.trim()).filter(Boolean);
7264
+ const query = new URLSearchParams();
7265
+ for (const key of watchList) {
7266
+ const val = urlParams.get(key);
7267
+ if (val !== null && val !== '') {
7268
+ query.set(key, val);
7269
+ }
7270
+ }
7271
+ return query.toString();
7272
+ }
7273
+ async fetchJobs() {
7274
+ this.isLoading = true;
7275
+ this.el.classList.add(this.loadingClass);
7276
+ const queryString = this.buildQueryString();
7277
+ const url = queryString ? `${this.apiUrl}?${queryString}` : this.apiUrl;
7278
+ try {
7279
+ const res = await fetch(url, {
7280
+ method: 'POST',
7281
+ headers: { 'Content-Type': 'application/json' },
7282
+ body: JSON.stringify({ disable_switch_search_mode: false }),
7283
+ });
7284
+ if (!res.ok)
7285
+ throw new Error('fetch failed');
7286
+ const data = await res.json();
7287
+ this.renderJobs(data.jobs);
7288
+ this.updateCountElements(data.jobs.length, data.totalJob);
7289
+ this.fetchComplete.emit({ jobs: data.jobs, totalJob: data.totalJob });
7290
+ }
7291
+ catch {
7292
+ // Preserve stale data on error
7293
+ }
7294
+ finally {
7295
+ this.isLoading = false;
7296
+ this.el.classList.remove(this.loadingClass);
7297
+ }
7298
+ }
7299
+ renderJobs(jobs) {
7300
+ if (!this.templateEl)
7301
+ return;
7302
+ // Remove all children except the template
7303
+ const children = Array.from(this.el.children);
7304
+ for (const child of children) {
7305
+ if (child !== this.templateEl) {
7306
+ child.remove();
7307
+ }
7308
+ }
7309
+ // Clone template and render each job
7310
+ for (const job of jobs) {
7311
+ const clone = this.templateEl.content.cloneNode(true);
7312
+ const jobCard = clone.querySelector('job-card');
7313
+ if (jobCard) {
7314
+ jobCard.setAttribute('job', JSON.stringify(job));
7315
+ }
7316
+ this.el.appendChild(clone);
7317
+ }
7318
+ }
7319
+ updateCountElements(count, total) {
7320
+ const countEls = document.querySelectorAll('[data-job-count]');
7321
+ const totalEls = document.querySelectorAll('[data-job-total]');
7322
+ countEls.forEach(el => {
7323
+ el.textContent = String(count);
7324
+ });
7325
+ totalEls.forEach(el => {
7326
+ el.textContent = String(total);
7327
+ });
7328
+ }
7329
+ render() {
7330
+ return hAsync("slot", { key: '9f3ab802e19a298a790cfb5e86a4f4888e466804' });
7331
+ }
7332
+ static get style() { return jobsListReactiveCss(); }
7333
+ static get cmpMeta() { return {
7334
+ "$flags$": 772,
7335
+ "$tagName$": "jobs-list-reactive",
7336
+ "$members$": {
7337
+ "apiUrl": [1, "api-url"],
7338
+ "watchParams": [1, "watch-params"],
7339
+ "loadingClass": [1, "loading-class"],
7340
+ "isLoading": [32]
6914
7341
  },
6915
7342
  "$listeners$": undefined,
6916
7343
  "$lazyBundleId$": "-",
@@ -6920,10 +7347,13 @@ class JobsListOnly {
6920
7347
 
6921
7348
  registerComponents([
6922
7349
  AppCarousel,
6923
- CustomButton,
7350
+ FastButton,
7351
+ FastForm,
7352
+ FastInput,
6924
7353
  JobCard,
6925
7354
  JobsItem,
6926
7355
  JobsListOnly,
7356
+ JobsListReactive,
6927
7357
  ]);
6928
7358
 
6929
7359
  exports.hydrateApp = hydrateApp;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phatvu/web-component-poc",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Stencil Component Starter",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.js",
@@ -24,14 +24,18 @@
24
24
  "types": "./dist/components/app-carousel.d.ts"
25
25
  },
26
26
  "./fast-button": {
27
- "import": "./dist/components/custom-button.js",
28
- "types": "./dist/components/custom-button.d.ts"
27
+ "import": "./dist/components/fast-button.js",
28
+ "types": "./dist/components/fast-button.d.ts"
29
29
  },
30
30
  "./loader": {
31
31
  "import": "./loader/index.js",
32
32
  "require": "./loader/index.cjs",
33
33
  "types": "./loader/index.d.ts"
34
34
  },
35
+ "./fast-input": {
36
+ "import": "./dist/components/fast-input.js",
37
+ "types": "./dist/components/fast-input.d.ts"
38
+ },
35
39
  "./jobs-list-only": {
36
40
  "import": "./dist/components/jobs-list-only.js",
37
41
  "types": "./dist/components/jobs-list-only.d.ts"