@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.
- package/dist/cjs/fast-button.cjs.entry.js +46 -0
- package/dist/cjs/{fast-button_4.cjs.entry.js → fast-carousel.cjs.entry.js} +1 -231
- package/dist/cjs/fast-input_4.cjs.entry.js +499 -0
- package/dist/cjs/{index-B2BTpdbN.js → index-BEvZs91D.js} +2 -2
- package/dist/cjs/job-card.cjs.entry.js +138 -0
- package/dist/cjs/loader.cjs.js +2 -2
- package/dist/cjs/web-component-poc.cjs.js +2 -2
- package/dist/collection/collection-manifest.json +4 -1
- package/dist/collection/components/button/button.js +2 -2
- package/dist/collection/components/fast-input/fast-input.css +55 -0
- package/dist/collection/components/fast-input/fast-input.js +335 -0
- package/dist/collection/components/job-card/job-card.css +247 -0
- package/dist/collection/components/job-card/job-card.js +435 -0
- package/dist/collection/components/jobs-item/jobs-item.js +5 -5
- package/dist/collection/components/jobs-list-only/jobs-list-only.js +185 -8
- package/dist/collection/components/jobs-list-reactive/jobs-list-reactive.css +8 -0
- package/dist/collection/components/jobs-list-reactive/jobs-list-reactive.js +203 -0
- package/dist/components/fast-button.js +1 -1
- package/dist/components/fast-carousel.js +1 -1
- package/dist/components/fast-input.d.ts +11 -0
- package/dist/components/fast-input.js +1 -0
- package/dist/components/index.js +1 -1
- package/dist/components/job-card.d.ts +11 -0
- package/dist/components/job-card.js +1 -0
- package/dist/components/jobs-item.js +1 -1
- package/dist/components/jobs-list-only.js +1 -1
- package/dist/components/jobs-list-reactive.d.ts +11 -0
- package/dist/components/jobs-list-reactive.js +1 -0
- package/dist/components/{p-ClQDwJJB.js → p-DQiaLjLf.js} +1 -1
- package/dist/esm/fast-button.entry.js +44 -0
- package/dist/esm/{fast-button_4.entry.js → fast-carousel.entry.js} +2 -229
- package/dist/esm/fast-input_4.entry.js +494 -0
- package/dist/esm/{index-Dk5CvWmb.js → index-C_ZLQIpp.js} +2 -2
- package/dist/esm/job-card.entry.js +136 -0
- package/dist/esm/loader.js +3 -3
- package/dist/esm/web-component-poc.js +3 -3
- package/dist/types/components/fast-input/fast-input.d.ts +37 -0
- package/dist/types/components/job-card/job-card.d.ts +93 -0
- package/dist/types/components/jobs-item/jobs-item.d.ts +2 -2
- package/dist/types/components/jobs-list-only/jobs-list-only.d.ts +24 -2
- package/dist/types/components/jobs-list-reactive/jobs-list-reactive.d.ts +26 -0
- package/dist/types/components.d.ts +469 -7
- package/dist/types/mock/jobs-list-only.mock.d.ts +2 -2
- package/dist/types/types/jobs-list.d.ts +6 -2
- package/dist/web-component-poc/p-618fba28.entry.js +1 -0
- package/dist/web-component-poc/p-7d45772f.entry.js +1 -0
- package/dist/web-component-poc/p-bef7c8e2.entry.js +1 -0
- package/dist/web-component-poc/p-cfb9aed9.entry.js +1 -0
- package/dist/web-component-poc/web-component-poc.esm.js +1 -1
- package/hydrate/index.js +534 -6
- package/hydrate/index.mjs +534 -6
- package/package.json +9 -1
- package/dist/web-component-poc/p-df843533.entry.js +0 -1
- /package/dist/components/{p-UM9TUfe3.js → p-BiaJAQXY.js} +0 -0
- /package/dist/web-component-poc/{p-Dk5CvWmb.js → p-C_ZLQIpp.js} +0 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import { r as registerInstance, c as createEvent, h, g as getElement } from './index-C_ZLQIpp.js';
|
|
2
|
+
|
|
3
|
+
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}`;
|
|
4
|
+
|
|
5
|
+
const FastInput = class {
|
|
6
|
+
constructor(hostRef) {
|
|
7
|
+
registerInstance(this, hostRef);
|
|
8
|
+
this.searchExecuted = createEvent(this, "searchExecuted");
|
|
9
|
+
this.inputChanged = createEvent(this, "inputChanged");
|
|
10
|
+
}
|
|
11
|
+
placeholder = 'Search...';
|
|
12
|
+
value = '';
|
|
13
|
+
paramName = 'keyword';
|
|
14
|
+
enableAutocomplete = false;
|
|
15
|
+
autocompleteUrl = '/api/jobs/autocomplete';
|
|
16
|
+
targetPath;
|
|
17
|
+
debounceMs = 300;
|
|
18
|
+
minChars = 3;
|
|
19
|
+
searchExecuted;
|
|
20
|
+
inputChanged;
|
|
21
|
+
inputValue = '';
|
|
22
|
+
suggestions = [];
|
|
23
|
+
showDropdown = false;
|
|
24
|
+
autocompleteLoading = false;
|
|
25
|
+
debounceTimer;
|
|
26
|
+
popstateHandler;
|
|
27
|
+
connectedCallback() {
|
|
28
|
+
const urlValue = this.getUrlParam();
|
|
29
|
+
this.inputValue = urlValue !== null ? urlValue : this.value;
|
|
30
|
+
this.popstateHandler = () => {
|
|
31
|
+
this.inputValue = this.getUrlParam() ?? '';
|
|
32
|
+
};
|
|
33
|
+
window.addEventListener('popstate', this.popstateHandler);
|
|
34
|
+
}
|
|
35
|
+
disconnectedCallback() {
|
|
36
|
+
window.removeEventListener('popstate', this.popstateHandler);
|
|
37
|
+
clearTimeout(this.debounceTimer);
|
|
38
|
+
}
|
|
39
|
+
getUrlParam() {
|
|
40
|
+
const params = new URLSearchParams(window.location.search);
|
|
41
|
+
return params.get(this.paramName);
|
|
42
|
+
}
|
|
43
|
+
updateUrlParam(value) {
|
|
44
|
+
const params = new URLSearchParams(window.location.search);
|
|
45
|
+
if (value) {
|
|
46
|
+
params.set(this.paramName, value);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
params.delete(this.paramName);
|
|
50
|
+
}
|
|
51
|
+
const qs = params.toString();
|
|
52
|
+
const newUrl = qs
|
|
53
|
+
? `${window.location.pathname}?${qs}`
|
|
54
|
+
: window.location.pathname;
|
|
55
|
+
history.pushState({}, '', newUrl);
|
|
56
|
+
}
|
|
57
|
+
submit() {
|
|
58
|
+
this.updateUrlParam(this.inputValue);
|
|
59
|
+
document.dispatchEvent(new CustomEvent('search-executed', {
|
|
60
|
+
detail: { keyword: this.inputValue },
|
|
61
|
+
bubbles: true,
|
|
62
|
+
composed: true,
|
|
63
|
+
}));
|
|
64
|
+
this.searchExecuted.emit({ keyword: this.inputValue });
|
|
65
|
+
this.showDropdown = false;
|
|
66
|
+
}
|
|
67
|
+
handleInput = (e) => {
|
|
68
|
+
const value = e.target.value;
|
|
69
|
+
this.inputValue = value;
|
|
70
|
+
this.inputChanged.emit({ value });
|
|
71
|
+
if (this.enableAutocomplete) {
|
|
72
|
+
this.scheduleAutocomplete(value);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
handleKeydown = (e) => {
|
|
76
|
+
if (e.key === 'Enter') {
|
|
77
|
+
this.submit();
|
|
78
|
+
}
|
|
79
|
+
else if (e.key === 'Escape') {
|
|
80
|
+
this.showDropdown = false;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
handleBlur = () => {
|
|
84
|
+
this.showDropdown = false;
|
|
85
|
+
};
|
|
86
|
+
scheduleAutocomplete(value) {
|
|
87
|
+
clearTimeout(this.debounceTimer);
|
|
88
|
+
if (value.length < this.minChars) {
|
|
89
|
+
this.showDropdown = false;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.debounceTimer = setTimeout(() => this.fetchSuggestions(value), this.debounceMs);
|
|
93
|
+
}
|
|
94
|
+
async fetchSuggestions(keyword) {
|
|
95
|
+
if (!this.targetPath) {
|
|
96
|
+
console.warn('[fast-input] target-path is required for autocomplete');
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.autocompleteLoading = true;
|
|
100
|
+
this.showDropdown = true;
|
|
101
|
+
try {
|
|
102
|
+
const res = await fetch(this.autocompleteUrl, {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
headers: { 'Content-Type': 'application/json' },
|
|
105
|
+
body: JSON.stringify({ keyword, target_path: this.targetPath }),
|
|
106
|
+
});
|
|
107
|
+
if (!res.ok)
|
|
108
|
+
throw new Error('autocomplete request failed');
|
|
109
|
+
const data = await res.json();
|
|
110
|
+
this.suggestions = data;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
this.showDropdown = false;
|
|
114
|
+
this.suggestions = [];
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
this.autocompleteLoading = false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
selectSuggestion(title) {
|
|
121
|
+
this.inputValue = title;
|
|
122
|
+
this.showDropdown = false;
|
|
123
|
+
this.submit();
|
|
124
|
+
}
|
|
125
|
+
render() {
|
|
126
|
+
return (h("div", { key: '3a9d31c7b109205600addc326d63979585f10bcd', class: "fast-input" }, h("input", { key: '8f238fe9e002f367d4939616be8c06d938d76045', type: "text", class: "fast-input__field", placeholder: this.placeholder, value: this.inputValue, onInput: this.handleInput, onKeyDown: this.handleKeydown, onBlur: this.handleBlur }), h("button", { key: '7b7404f13432750ece669da8ce68be15179921de', class: "fast-input__button", type: "button", onClick: () => this.submit() }, "Search"), this.enableAutocomplete && this.showDropdown && (h("ul", { key: '1438bacadc21c183842a8bdaa3f336bffb152e14', class: "fast-input__dropdown" }, this.autocompleteLoading ? (h("li", { class: "fast-input__dropdown-loading" }, "Loading...")) : (this.suggestions.map(s => (h("li", { class: "fast-input__dropdown-item", onMouseDown: e => { e.preventDefault(); }, onClick: () => this.selectSuggestion(s.title) }, s.title))))))));
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
FastInput.style = fastInputCss();
|
|
130
|
+
|
|
131
|
+
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}`;
|
|
132
|
+
|
|
133
|
+
function getLocationLabel(loc) {
|
|
134
|
+
if (loc.cityStateAbbr)
|
|
135
|
+
return loc.cityStateAbbr;
|
|
136
|
+
const parts = [loc.streetAddress, loc.city, loc.stateAbbr || loc.state, loc.countryAbbr || loc.country].filter(Boolean);
|
|
137
|
+
return parts.join(', ') || loc.locationText || '';
|
|
138
|
+
}
|
|
139
|
+
function getFirstLocation(job) {
|
|
140
|
+
const locs = job.locations;
|
|
141
|
+
if (!locs?.length)
|
|
142
|
+
return undefined;
|
|
143
|
+
return locs[0];
|
|
144
|
+
}
|
|
145
|
+
const JobsItem = class {
|
|
146
|
+
constructor(hostRef) {
|
|
147
|
+
registerInstance(this, hostRef);
|
|
148
|
+
}
|
|
149
|
+
job;
|
|
150
|
+
index = 0;
|
|
151
|
+
applyButtonText = 'Apply Now';
|
|
152
|
+
showBrand = true;
|
|
153
|
+
showReference = false;
|
|
154
|
+
showEmploymentType = true;
|
|
155
|
+
multiLocationText = 'More locations';
|
|
156
|
+
remoteLocationText = 'Remote';
|
|
157
|
+
enableKilometers = false;
|
|
158
|
+
formatDistance(distance) {
|
|
159
|
+
const value = this.enableKilometers ? distance * 1.60934 : distance;
|
|
160
|
+
const unit = this.enableKilometers ? 'Km' : 'Miles';
|
|
161
|
+
const str = `${value.toFixed(1)} ${unit}`.replace('.0', '');
|
|
162
|
+
return str;
|
|
163
|
+
}
|
|
164
|
+
render() {
|
|
165
|
+
if (!this.job)
|
|
166
|
+
return null;
|
|
167
|
+
const firstLoc = getFirstLocation(this.job);
|
|
168
|
+
const locationLabel = firstLoc ? getLocationLabel(firstLoc) : '';
|
|
169
|
+
const distance = firstLoc?.distance ?? 0;
|
|
170
|
+
const distanceLabel = distance > 0 ? this.formatDistance(distance) : '';
|
|
171
|
+
const applyHref = this.job.applyURL ||
|
|
172
|
+
(this.job.originalURL ? `${typeof window !== 'undefined' ? window.location.origin : ''}${this.job.originalURL}` : '#');
|
|
173
|
+
const applyLabel = `${this.applyButtonText}, ${this.job.title || ''}`;
|
|
174
|
+
const locs = this.job.locations ?? [];
|
|
175
|
+
const hasMultipleLocations = locs.length > 1;
|
|
176
|
+
return (h("li", { class: "results-list__item" }, h("div", { class: "results-list__item-header" }, h("h3", { class: "results-list__item-title" }, h("a", { class: "results-list__item-title--link", href: applyHref, target: "_blank", rel: "noopener noreferrer" }, this.job.title || ''), this.showReference && (h("span", { class: `reference ${this.job.reference ? '' : 'empty'}` }, this.job.reference || '')), this.job.isRemote && (h("span", { class: this.remoteLocationText ? 'remote' : 'remote remote--empty' }, this.remoteLocationText))), distanceLabel && (h("div", { class: "results-list__item-distance" }, h("span", { class: "results-list__item-distance--icon" }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18z" }), h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M12 8v4l2 2" }))), h("span", { class: "results-list__item-distance--label" }, distanceLabel)))), h("div", { class: "results-list__item-content" }, h("div", { class: "results-list__item-info" }, h("div", { class: locs.length
|
|
177
|
+
? 'results-list__item-street'
|
|
178
|
+
: 'results-list__item-street results-list__item-street--empty' }, h("div", { class: "results-list__item-street--label__wrapper" }, h("span", { class: "results-list__item-street--icon" }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0z" }), h("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" }))), h("span", { class: "results-list__item-street--label" }, locationLabel || '—')), hasMultipleLocations && (h("div", { class: "results-list__item-street--more-locations__wrapper" }, h("span", { class: "results-list__item-street--amount" }, "+", locs.length - 1), h("span", { class: "results-list__item-street--more-locations" }, this.multiLocationText)))), this.showBrand && (h("div", { class: this.job.brandName
|
|
179
|
+
? 'results-list__item-brand'
|
|
180
|
+
: 'results-list__item-brand results-list__item-brand--empty' }, h("span", { class: "results-list__item-brand--icon" }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5" }, h("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" }))), h("span", { class: "results-list__item-brand--label" }, this.job.brandName || '—'))), this.showEmploymentType && (h("div", { class: this.job.employmentType?.length
|
|
181
|
+
? 'results-list__item-employment-type'
|
|
182
|
+
: 'results-list__item-employment-type results-list__item-employment-type--empty' }, h("span", { class: "results-list__item-employment-type--icon" }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5" }, h("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" }))), (this.job.employmentType?.length ? this.job.employmentType : ['—']).map((type) => (h("span", { key: type, class: "results-list__item-employment-type--label" }, type))))), (this.job.jobCardExtraFields ?? []).map((field, i) => (h("div", { key: i, class: (Array.isArray(field.value) ? field.value.length : field.value)
|
|
183
|
+
? field.classname
|
|
184
|
+
: `${field.classname}--empty` }, Array.isArray(field.value)
|
|
185
|
+
? field.value.map((v, j) => (h("span", { key: j, class: `${field.classname}--label` }, v)))
|
|
186
|
+
: (h("span", { class: `${field.classname}--label` }, String(field.value))))))), h("a", { class: "results-list__item-apply", href: applyHref, target: "_blank", rel: "noopener noreferrer", "aria-label": applyLabel }, h("span", { class: "results-list__item-apply--label" }, this.applyButtonText), h("span", { class: "results-list__item-apply--icon" }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M13 7l5 5m0 0l-5 5m5-5H6" })))))));
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
JobsItem.style = jobsItemCss();
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Mock jobs for local development and docs.
|
|
193
|
+
* Use in www or static HTML: jobs='...' (JSON.stringify(mockJobs)).
|
|
194
|
+
*/
|
|
195
|
+
const mockJobsListOnly = [
|
|
196
|
+
{
|
|
197
|
+
title: 'Senior Software Engineer',
|
|
198
|
+
reference: 'REF-001',
|
|
199
|
+
originalURL: '/jobs/senior-software-engineer',
|
|
200
|
+
applyURL: 'https://apply.example.com/1',
|
|
201
|
+
brandName: 'Engineering',
|
|
202
|
+
isRemote: false,
|
|
203
|
+
locations: [
|
|
204
|
+
{
|
|
205
|
+
city: 'San Francisco',
|
|
206
|
+
stateAbbr: 'CA',
|
|
207
|
+
countryAbbr: 'US',
|
|
208
|
+
distance: 5.2,
|
|
209
|
+
streetAddress: '123 Market St',
|
|
210
|
+
cityStateAbbr: 'San Francisco, CA',
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
employmentType: ['Full-time', 'Permanent'],
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
title: 'Product Manager',
|
|
217
|
+
reference: '',
|
|
218
|
+
originalURL: '/jobs/product-manager',
|
|
219
|
+
brandName: 'Product',
|
|
220
|
+
isRemote: true,
|
|
221
|
+
locations: [],
|
|
222
|
+
employmentType: ['Full-time'],
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
title: 'UX Designer',
|
|
226
|
+
reference: 'REF-003',
|
|
227
|
+
originalURL: '/jobs/ux-designer',
|
|
228
|
+
brandName: 'Design',
|
|
229
|
+
isRemote: false,
|
|
230
|
+
locations: [
|
|
231
|
+
{
|
|
232
|
+
city: 'New York',
|
|
233
|
+
stateAbbr: 'NY',
|
|
234
|
+
countryAbbr: 'US',
|
|
235
|
+
distance: 0,
|
|
236
|
+
cityStateAbbr: 'New York, NY',
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
city: 'Boston',
|
|
240
|
+
stateAbbr: 'MA',
|
|
241
|
+
countryAbbr: 'US',
|
|
242
|
+
cityStateAbbr: 'Boston, MA',
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
employmentType: ['Full-time', 'Contract'],
|
|
246
|
+
},
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
const jobsListOnlyCss = () => `:host{display:block}.jobs-list-root{list-style:none}.results-container{position:relative}.loader{display:inline-block;width:24px;height:24px;border:2px solid #ddd;border-top-color:#1f9755;border-radius:50%;animation:jobs-list-spin 0.8s linear infinite}.loader.hide{display:none}@keyframes jobs-list-spin{to{transform:rotate(360deg)}}.card{border:0}.results-list{list-style:none;margin:0;padding:0;display:block}.results-list.front{margin:3px 0}.share-jobs__no-results{padding:24px;text-align:center}.share-jobs__no-results h2,.share-jobs__no-results h3{margin:8px 0;font-weight:600}.card.primary-color{padding:16px;border-radius:4px;background:#f8f9fa}.result-suggestions-title{margin:0 0 12px 0;font-size:16px}.results-list .result-suggestions-line{list-style:none;margin:4px 0}`;
|
|
250
|
+
|
|
251
|
+
const defaultNoResultsLine1 = "Sorry, we're not able to load results for your search.";
|
|
252
|
+
const defaultNoResultsLine2 = 'Please refine your keywords in the search bar above and try again.';
|
|
253
|
+
const JobsListOnly = class {
|
|
254
|
+
constructor(hostRef) {
|
|
255
|
+
registerInstance(this, hostRef);
|
|
256
|
+
this.fetchComplete = createEvent(this, "fetchComplete");
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* When "true", use built-in mock data (for local/dev/docs). Otherwise use `jobs` from API/parent.
|
|
260
|
+
*/
|
|
261
|
+
mockData = false;
|
|
262
|
+
/** List of jobs to display. Pass as JSON string from attribute or as array via property (e.g. from React). Ignored when mock-data="true". */
|
|
263
|
+
jobs = [];
|
|
264
|
+
/** Show loading spinner. Ignored when mock-data="true" (mock shows data immediately). */
|
|
265
|
+
loading = false;
|
|
266
|
+
/** Total job count (for screen readers / schema). */
|
|
267
|
+
totalJob = 0;
|
|
268
|
+
noResultsLine1 = defaultNoResultsLine1;
|
|
269
|
+
noResultsLine2 = defaultNoResultsLine2;
|
|
270
|
+
applyButtonText = 'Apply Now';
|
|
271
|
+
showBrand = true;
|
|
272
|
+
showReference = false;
|
|
273
|
+
showEmploymentType = true;
|
|
274
|
+
streetFormat = '{street}, {city_state_abbr}';
|
|
275
|
+
multiLocationText = 'More locations';
|
|
276
|
+
remoteLocationText = 'Remote';
|
|
277
|
+
enableKilometers = false;
|
|
278
|
+
/** Extra CSS class on the root element (avoid prop name "class" / "classname" reserved). */
|
|
279
|
+
rootClass = '';
|
|
280
|
+
/** Template string for count display. Tokens: {count} = jobs on page, {total} = total from API. */
|
|
281
|
+
showCountText = '';
|
|
282
|
+
showSuggestions = false;
|
|
283
|
+
clearResultSuggestionsTitleText = 'Suggestions';
|
|
284
|
+
clearResultSuggestionsLine1 = 'Try different keywords';
|
|
285
|
+
clearResultSuggestionsLine2 = 'Make sure everything is spelled correctly';
|
|
286
|
+
clearResultSuggestionsLine3 = 'Try other locations';
|
|
287
|
+
clearResultSuggestionsLine4 = '';
|
|
288
|
+
/** When true, component manages its own data fetching */
|
|
289
|
+
autoFetch = false;
|
|
290
|
+
/** Jobs search endpoint */
|
|
291
|
+
apiUrl = '/api/get-jobs';
|
|
292
|
+
/** Comma-separated URL param names to watch and forward to the API */
|
|
293
|
+
watchParams = 'keyword';
|
|
294
|
+
fetchedJobs = [];
|
|
295
|
+
fetchedTotal = 0;
|
|
296
|
+
fetchLoading = false;
|
|
297
|
+
fetchComplete;
|
|
298
|
+
searchExecutedHandler;
|
|
299
|
+
popstateHandler;
|
|
300
|
+
connectedCallback() {
|
|
301
|
+
if (this.autoFetch) {
|
|
302
|
+
this.fetchJobs();
|
|
303
|
+
this.searchExecutedHandler = () => this.fetchJobs();
|
|
304
|
+
this.popstateHandler = () => this.fetchJobs();
|
|
305
|
+
document.addEventListener('search-executed', this.searchExecutedHandler);
|
|
306
|
+
window.addEventListener('popstate', this.popstateHandler);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
disconnectedCallback() {
|
|
310
|
+
if (this.autoFetch) {
|
|
311
|
+
document.removeEventListener('search-executed', this.searchExecutedHandler);
|
|
312
|
+
window.removeEventListener('popstate', this.popstateHandler);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async fetchJobs() {
|
|
316
|
+
this.fetchLoading = true;
|
|
317
|
+
const params = new URLSearchParams(window.location.search);
|
|
318
|
+
const watchList = this.watchParams.split(',').map(p => p.trim()).filter(Boolean);
|
|
319
|
+
const query = new URLSearchParams();
|
|
320
|
+
for (const key of watchList) {
|
|
321
|
+
const val = params.get(key);
|
|
322
|
+
if (val !== null)
|
|
323
|
+
query.set(key, val);
|
|
324
|
+
}
|
|
325
|
+
const url = `${this.apiUrl}?${query.toString()}`;
|
|
326
|
+
try {
|
|
327
|
+
const res = await fetch(url, {
|
|
328
|
+
method: 'POST',
|
|
329
|
+
headers: { 'Content-Type': 'application/json' },
|
|
330
|
+
body: JSON.stringify({ disable_switch_search_mode: false }),
|
|
331
|
+
});
|
|
332
|
+
if (!res.ok)
|
|
333
|
+
throw new Error('fetch failed');
|
|
334
|
+
const data = await res.json();
|
|
335
|
+
this.fetchedJobs = data.jobs;
|
|
336
|
+
this.fetchedTotal = data.totalJob;
|
|
337
|
+
this.fetchComplete.emit({ jobs: data.jobs, totalJob: data.totalJob });
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
// preserve stale data, just stop loading
|
|
341
|
+
}
|
|
342
|
+
finally {
|
|
343
|
+
this.fetchLoading = false;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
renderCountText(count, total) {
|
|
347
|
+
return this.showCountText
|
|
348
|
+
.replace('{count}', String(count))
|
|
349
|
+
.replace('{total}', String(total));
|
|
350
|
+
}
|
|
351
|
+
getJobsArray() {
|
|
352
|
+
if (this.mockData) {
|
|
353
|
+
return mockJobsListOnly;
|
|
354
|
+
}
|
|
355
|
+
if (this.autoFetch) {
|
|
356
|
+
return this.fetchedJobs;
|
|
357
|
+
}
|
|
358
|
+
const j = this.jobs;
|
|
359
|
+
if (Array.isArray(j))
|
|
360
|
+
return j;
|
|
361
|
+
if (typeof j === 'string') {
|
|
362
|
+
try {
|
|
363
|
+
const parsed = JSON.parse(j);
|
|
364
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
return [];
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return [];
|
|
371
|
+
}
|
|
372
|
+
renderJobItem(job, index) {
|
|
373
|
+
return (h("jobs-item", { job: job, index: index, applyButtonText: this.applyButtonText, showBrand: this.showBrand, showReference: this.showReference, showEmploymentType: this.showEmploymentType, multiLocationText: this.multiLocationText, remoteLocationText: this.remoteLocationText, enableKilometers: this.enableKilometers }));
|
|
374
|
+
}
|
|
375
|
+
render() {
|
|
376
|
+
const jobsArray = this.getJobsArray();
|
|
377
|
+
const loading = this.mockData ? false : (this.autoFetch ? this.fetchLoading : this.loading);
|
|
378
|
+
const totalJob = this.mockData
|
|
379
|
+
? jobsArray.length
|
|
380
|
+
: this.autoFetch
|
|
381
|
+
? this.fetchedTotal
|
|
382
|
+
: (this.totalJob || jobsArray.length);
|
|
383
|
+
const showNoResults = !loading && totalJob === 0 && !this.showSuggestions;
|
|
384
|
+
const showSuggestionsBlock = !loading && totalJob === 0 && this.showSuggestions;
|
|
385
|
+
return (h("div", { key: '1116855473d28d650641b9d962243bfcdcb434ec', class: `jobs-list-root ${this.rootClass}`.trim() }, h("div", { key: 'fcef04f1da9ad4e150af9f59921688f5781d9d43', class: "results-container" }, this.autoFetch && this.fetchLoading && (h("div", { key: '75b157c82c89691c7ce73d12ea0144b3b45485c3', class: "jobs-list-only__loading" }, "Loading...")), h("div", { key: 'b09f9879e10ee4a93e32177611912da5f19f3526', class: loading ? 'loader' : 'loader hide', "aria-hidden": !loading }), totalJob > 0 && this.showCountText && (h("p", { key: 'd10f800fb0a33d82531d5f1728bac4ceba2ed577', class: "jobs-list-only__count" }, this.renderCountText(jobsArray.length, totalJob))), totalJob > 0 && (h("div", { key: '18153ed1338bd48f7be4f043b11ce15e3271f27b', class: "card" }, h("ul", { key: '766e128b1fd5adb456530ae39e92ba8eb0b5d6cf', class: "results-list front" }, jobsArray.map((job, index) => this.renderJobItem(job, index))))), showNoResults && (h("div", { key: 'ed6f3d2bd2bbedabd6e69d508ea1425580e6941f', class: "share-jobs__no-results" }, h("h2", { key: '2302656e33340c69e84cb949afb7256b8f35f440' }, this.noResultsLine1), h("h3", { key: '1c7e6642441a96c04ee26883fdec4f81b0fe6cec' }, this.noResultsLine2))), showSuggestionsBlock && (h("div", { key: 'be7af85f64455918545e88952ca6ff00f0a970c5', class: "card primary-color" }, h("h4", { key: '2f63deb8131190eff882308544b15f767b6f3edc', class: "result-suggestions-title" }, this.clearResultSuggestionsTitleText, ":"), h("ul", { key: 'cb18daaa9e2c2c442c5b906ed370dcd653b5262d', class: "results-list front" }, h("li", { key: 'ff1d5c6518b75c0daa35b43df7162b0dfecde25e', class: "result-suggestions-line" }, this.clearResultSuggestionsLine1), h("li", { key: '4099fd7bf8dcf114eca28702a498ab0938f5de46', class: "result-suggestions-line" }, this.clearResultSuggestionsLine2), h("li", { key: 'fb65b54c3c0b14bc58112977eb4c7c56c1246a45', class: "result-suggestions-line" }, this.clearResultSuggestionsLine3), this.clearResultSuggestionsLine4 && (h("li", { key: '10f745e74cf68a2b1c42e6f49f810a8b59eb27b6', class: "result-suggestions-line" }, this.clearResultSuggestionsLine4))))))));
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
JobsListOnly.style = jobsListOnlyCss();
|
|
389
|
+
|
|
390
|
+
const jobsListReactiveCss = () => `jobs-list-reactive{display:block}jobs-list-reactive.loading{opacity:0.6;pointer-events:none}`;
|
|
391
|
+
|
|
392
|
+
const JobsListReactive = class {
|
|
393
|
+
constructor(hostRef) {
|
|
394
|
+
registerInstance(this, hostRef);
|
|
395
|
+
this.fetchComplete = createEvent(this, "fetchComplete");
|
|
396
|
+
}
|
|
397
|
+
get el() { return getElement(this); }
|
|
398
|
+
/** Jobs search endpoint */
|
|
399
|
+
apiUrl = '/api/get-jobs';
|
|
400
|
+
/** Comma-separated URL param names to watch and forward to the API */
|
|
401
|
+
watchParams = 'keyword,location_name';
|
|
402
|
+
/** CSS class added to container while fetching */
|
|
403
|
+
loadingClass = 'loading';
|
|
404
|
+
isLoading = false;
|
|
405
|
+
fetchComplete;
|
|
406
|
+
templateEl = null;
|
|
407
|
+
searchExecutedHandler;
|
|
408
|
+
popstateHandler;
|
|
409
|
+
connectedCallback() {
|
|
410
|
+
this.templateEl = this.el.querySelector('template');
|
|
411
|
+
this.searchExecutedHandler = () => this.fetchJobs();
|
|
412
|
+
this.popstateHandler = () => this.fetchJobs();
|
|
413
|
+
document.addEventListener('search-executed', this.searchExecutedHandler);
|
|
414
|
+
window.addEventListener('popstate', this.popstateHandler);
|
|
415
|
+
}
|
|
416
|
+
disconnectedCallback() {
|
|
417
|
+
document.removeEventListener('search-executed', this.searchExecutedHandler);
|
|
418
|
+
window.removeEventListener('popstate', this.popstateHandler);
|
|
419
|
+
}
|
|
420
|
+
buildQueryString() {
|
|
421
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
422
|
+
const watchList = this.watchParams.split(',').map(p => p.trim()).filter(Boolean);
|
|
423
|
+
const query = new URLSearchParams();
|
|
424
|
+
for (const key of watchList) {
|
|
425
|
+
const val = urlParams.get(key);
|
|
426
|
+
if (val !== null && val !== '') {
|
|
427
|
+
query.set(key, val);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return query.toString();
|
|
431
|
+
}
|
|
432
|
+
async fetchJobs() {
|
|
433
|
+
this.isLoading = true;
|
|
434
|
+
this.el.classList.add(this.loadingClass);
|
|
435
|
+
const queryString = this.buildQueryString();
|
|
436
|
+
const url = queryString ? `${this.apiUrl}?${queryString}` : this.apiUrl;
|
|
437
|
+
try {
|
|
438
|
+
const res = await fetch(url, {
|
|
439
|
+
method: 'POST',
|
|
440
|
+
headers: { 'Content-Type': 'application/json' },
|
|
441
|
+
body: JSON.stringify({ disable_switch_search_mode: false }),
|
|
442
|
+
});
|
|
443
|
+
if (!res.ok)
|
|
444
|
+
throw new Error('fetch failed');
|
|
445
|
+
const data = await res.json();
|
|
446
|
+
this.renderJobs(data.jobs);
|
|
447
|
+
this.updateCountElements(data.jobs.length, data.totalJob);
|
|
448
|
+
this.fetchComplete.emit({ jobs: data.jobs, totalJob: data.totalJob });
|
|
449
|
+
}
|
|
450
|
+
catch {
|
|
451
|
+
// Preserve stale data on error
|
|
452
|
+
}
|
|
453
|
+
finally {
|
|
454
|
+
this.isLoading = false;
|
|
455
|
+
this.el.classList.remove(this.loadingClass);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
renderJobs(jobs) {
|
|
459
|
+
if (!this.templateEl)
|
|
460
|
+
return;
|
|
461
|
+
// Remove all children except the template
|
|
462
|
+
const children = Array.from(this.el.children);
|
|
463
|
+
for (const child of children) {
|
|
464
|
+
if (child !== this.templateEl) {
|
|
465
|
+
child.remove();
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// Clone template and render each job
|
|
469
|
+
for (const job of jobs) {
|
|
470
|
+
const clone = this.templateEl.content.cloneNode(true);
|
|
471
|
+
const jobCard = clone.querySelector('job-card');
|
|
472
|
+
if (jobCard) {
|
|
473
|
+
jobCard.setAttribute('job', JSON.stringify(job));
|
|
474
|
+
}
|
|
475
|
+
this.el.appendChild(clone);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
updateCountElements(count, total) {
|
|
479
|
+
const countEls = document.querySelectorAll('[data-job-count]');
|
|
480
|
+
const totalEls = document.querySelectorAll('[data-job-total]');
|
|
481
|
+
countEls.forEach(el => {
|
|
482
|
+
el.textContent = String(count);
|
|
483
|
+
});
|
|
484
|
+
totalEls.forEach(el => {
|
|
485
|
+
el.textContent = String(total);
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
render() {
|
|
489
|
+
return h("slot", { key: '30a6fe9727eb877b6aafb99072c40811df121ba6' });
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
JobsListReactive.style = jobsListReactiveCss();
|
|
493
|
+
|
|
494
|
+
export { FastInput as fast_input, JobsItem as jobs_item, JobsListOnly as jobs_list_only, JobsListReactive as jobs_list_reactive };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const NAMESPACE = 'web-component-poc';
|
|
2
|
-
const BUILD = /* web-component-poc */ { hotModuleReplacement: false, hydratedSelectorName: "hydrated", lazyLoad: true,
|
|
2
|
+
const BUILD = /* web-component-poc */ { hotModuleReplacement: false, hydratedSelectorName: "hydrated", lazyLoad: true, propChangeCallback: false, shadowDom: false, slotRelocation: true, state: true, updatable: true};
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
5
|
Stencil Client Platform v4.43.2 | MIT Licensed | https://stenciljs.com
|
|
@@ -86,7 +86,7 @@ var registerInstance = (lazyInstance, hostRef) => {
|
|
|
86
86
|
if (!hostRef) return;
|
|
87
87
|
lazyInstance.__stencil__getHostRef = () => hostRef;
|
|
88
88
|
hostRef.$lazyInstance$ = lazyInstance;
|
|
89
|
-
if (hostRef.$cmpMeta$.$flags$ & 512 /* hasModernPropertyDecls */ && (BUILD.
|
|
89
|
+
if (hostRef.$cmpMeta$.$flags$ & 512 /* hasModernPropertyDecls */ && (BUILD.state)) {
|
|
90
90
|
reWireGetterSetter(lazyInstance, hostRef);
|
|
91
91
|
}
|
|
92
92
|
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { r as registerInstance, h } from './index-C_ZLQIpp.js';
|
|
2
|
+
|
|
3
|
+
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}}`;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Helper function to get formatted location label from location object
|
|
7
|
+
*/
|
|
8
|
+
function getLocationLabel(loc) {
|
|
9
|
+
if (loc.cityStateAbbr)
|
|
10
|
+
return loc.cityStateAbbr;
|
|
11
|
+
const parts = [loc.streetAddress, loc.city, loc.stateAbbr || loc.state, loc.countryAbbr || loc.country].filter(Boolean);
|
|
12
|
+
return parts.join(', ') || loc.locationText || '';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Helper function to get the first location from job locations
|
|
16
|
+
*/
|
|
17
|
+
function getFirstLocation(job) {
|
|
18
|
+
const locs = job.locations;
|
|
19
|
+
if (!locs?.length)
|
|
20
|
+
return undefined;
|
|
21
|
+
return locs[0];
|
|
22
|
+
}
|
|
23
|
+
const JobCard = class {
|
|
24
|
+
constructor(hostRef) {
|
|
25
|
+
registerInstance(this, hostRef);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The job data object to display. Accepts either a Job object or a JSON string.
|
|
29
|
+
*/
|
|
30
|
+
job;
|
|
31
|
+
/**
|
|
32
|
+
* Index of the job in a list (used for accessibility)
|
|
33
|
+
*/
|
|
34
|
+
index = 0;
|
|
35
|
+
/**
|
|
36
|
+
* Text for the apply button
|
|
37
|
+
*/
|
|
38
|
+
applyButtonText = 'Apply Now';
|
|
39
|
+
/**
|
|
40
|
+
* Whether to show the brand/company name
|
|
41
|
+
*/
|
|
42
|
+
showBrand = true;
|
|
43
|
+
/**
|
|
44
|
+
* Whether to show the reference number
|
|
45
|
+
*/
|
|
46
|
+
showReference = false;
|
|
47
|
+
/**
|
|
48
|
+
* Whether to show employment type
|
|
49
|
+
*/
|
|
50
|
+
showEmploymentType = true;
|
|
51
|
+
/**
|
|
52
|
+
* Text shown for multiple locations
|
|
53
|
+
*/
|
|
54
|
+
multiLocationText = 'More locations';
|
|
55
|
+
/**
|
|
56
|
+
* Text shown for remote jobs
|
|
57
|
+
*/
|
|
58
|
+
remoteLocationText = 'Remote';
|
|
59
|
+
/**
|
|
60
|
+
* Whether to show distances in kilometers instead of miles
|
|
61
|
+
*/
|
|
62
|
+
enableKilometers = false;
|
|
63
|
+
/**
|
|
64
|
+
* Whether to show commute time
|
|
65
|
+
*/
|
|
66
|
+
showCommuteTime = false;
|
|
67
|
+
/**
|
|
68
|
+
* Format string for street address (not used in base web component)
|
|
69
|
+
*/
|
|
70
|
+
streetFormat = '{street}, {city_state_abbr}';
|
|
71
|
+
/** Extra CSS class on the root inner element (avoid prop name "className", reserved on HTMLElement). */
|
|
72
|
+
rootClass = '';
|
|
73
|
+
/**
|
|
74
|
+
* Custom extra fields configuration
|
|
75
|
+
*/
|
|
76
|
+
extraFieldsConfig = [];
|
|
77
|
+
/**
|
|
78
|
+
* Format distance with unit
|
|
79
|
+
*/
|
|
80
|
+
formatDistance(distance) {
|
|
81
|
+
const value = this.enableKilometers ? distance * 1.60934 : distance;
|
|
82
|
+
const unit = this.enableKilometers ? 'Km' : 'Miles';
|
|
83
|
+
const str = `${value.toFixed(1)} ${unit}`.replace('.0', '');
|
|
84
|
+
return str;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Is Empty utility
|
|
88
|
+
*/
|
|
89
|
+
isEmpty(value) {
|
|
90
|
+
if (value === null || value === undefined || value === '') {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Parse job data from prop - handles both object and JSON string
|
|
100
|
+
*/
|
|
101
|
+
getJobData() {
|
|
102
|
+
if (!this.job)
|
|
103
|
+
return null;
|
|
104
|
+
if (typeof this.job === 'string') {
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(this.job);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
console.warn('job-card: Failed to parse job JSON string');
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return this.job;
|
|
114
|
+
}
|
|
115
|
+
render() {
|
|
116
|
+
const job = this.getJobData();
|
|
117
|
+
if (!job)
|
|
118
|
+
return null;
|
|
119
|
+
const firstLoc = getFirstLocation(job);
|
|
120
|
+
const locationLabel = firstLoc ? getLocationLabel(firstLoc) : '';
|
|
121
|
+
const distance = firstLoc?.distance ?? 0;
|
|
122
|
+
const distanceLabel = distance > 0 ? this.formatDistance(distance) : '';
|
|
123
|
+
const applyHref = job.applyURL ||
|
|
124
|
+
(job.originalURL ? `${typeof window !== 'undefined' ? window.location.origin : ''}${job.originalURL}` : '#');
|
|
125
|
+
const applyLabel = `${this.applyButtonText}, ${job.title || ''}`;
|
|
126
|
+
const locs = job.locations ?? [];
|
|
127
|
+
const hasMultipleLocations = locs.length > 1;
|
|
128
|
+
return (h("div", { class: `job-card ${this.rootClass}`.trim() }, h("div", { class: "job-card__header" }, h("h3", { class: "job-card__title" }, h("a", { class: "job-card__title--link", href: applyHref, target: "_blank", rel: "noopener noreferrer" }, job.title || ''), this.showReference && (h("span", { class: `job-card__reference ${job.reference ? '' : 'job-card__reference--empty'}` }, job.reference || '')), job.isRemote && (h("span", { class: this.remoteLocationText ? 'job-card__remote' : 'job-card__remote job-card__remote--empty' }, this.remoteLocationText))), distanceLabel && (h("div", { class: "job-card__distance" }, h("span", { class: "job-card__distance--icon" }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18z" }), h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M12 8v4l2 2" }))), h("span", { class: "job-card__distance--label" }, distanceLabel)))), h("div", { class: "job-card__content" }, h("div", { class: "job-card__info" }, h("div", { class: locs.length ? 'job-card__street' : 'job-card__street job-card__street--empty' }, h("div", { class: "job-card__street--label__wrapper" }, h("span", { class: "job-card__street--icon" }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0z" }), h("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" }))), h("span", { class: "job-card__street--label" }, locationLabel || '—')), hasMultipleLocations && (h("div", { class: "job-card__street--more-locations__wrapper" }, h("span", { class: "job-card__street--amount" }, "+", locs.length - 1), h("span", { class: "job-card__street--more-locations" }, this.multiLocationText)))), this.showBrand && (h("div", { class: job.brandName ? 'job-card__brand' : 'job-card__brand job-card__brand--empty' }, h("span", { class: "job-card__brand--icon" }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5" }, h("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" }))), h("span", { class: "job-card__brand--label" }, job.brandName || '—'))), this.showEmploymentType && (h("div", { class: job.employmentType?.length
|
|
129
|
+
? 'job-card__employment-type'
|
|
130
|
+
: 'job-card__employment-type job-card__employment-type--empty' }, h("span", { class: "job-card__employment-type--icon" }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "1.5" }, h("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) => (h("span", { key: type, class: "job-card__employment-type--label" }, type)))) : (h("span", { class: "job-card__employment-type--label" }, "\u2014")))), (job.jobCardExtraFields ?? []).length > 0 &&
|
|
131
|
+
job.jobCardExtraFields?.map((field, idx) => (h("div", { key: idx, class: !this.isEmpty(field.value) ? `${field.classname}` : `${field.classname}--empty` }, Array.isArray(field.value) ? (field.value.map((item, itemIdx) => (h("span", { key: itemIdx, class: `${field.classname}--label` }, item)))) : (h("span", { class: `${field.classname}--label` }, field.value)))))), h("a", { class: "job-card__apply", href: applyHref, target: "_blank", rel: "noopener noreferrer", "aria-label": applyLabel }, h("span", { class: "job-card__apply--label" }, this.applyButtonText), h("span", { class: "job-card__apply--icon" }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, h("path", { "stroke-linecap": "round", "stroke-linejoin": "round", d: "M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3" })))))));
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
JobCard.style = jobCardCss();
|
|
135
|
+
|
|
136
|
+
export { JobCard as job_card };
|