soaring-symbols 0.2.1 โ 0.2.3
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/API.md +30 -19
- package/README.md +19 -12
- package/dist/airlines.json +1 -1
- package/dist/assets/air-arabia/icon-mono.svg +1 -1
- package/dist/assets/air-arabia/icon.svg +1 -1
- package/dist/assets/air-berlin/icon-mono.svg +1 -1
- package/dist/assets/air-berlin/icon.svg +1 -1
- package/dist/assets/air-berlin/logo-mono.svg +1 -1
- package/dist/assets/air-berlin/logo.svg +1 -1
- package/dist/assets/airasia-cambodia/logo-mono.svg +1 -0
- package/dist/assets/airasia-cambodia/logo.svg +1 -0
- package/dist/assets/american-airlines/icon.svg +1 -1
- package/dist/assets/american-airlines/logo.svg +1 -1
- package/dist/assets/austrian-airlines/logo.svg +1 -1
- package/dist/assets/avianca-costa-rica/icon-mono.svg +1 -0
- package/dist/assets/avianca-costa-rica/icon.svg +1 -0
- package/dist/assets/avianca-costa-rica/logo-mono.svg +1 -0
- package/dist/assets/avianca-costa-rica/logo.svg +1 -0
- package/dist/assets/avianca-ecuador/icon-mono.svg +1 -0
- package/dist/assets/avianca-ecuador/icon.svg +1 -0
- package/dist/assets/avianca-ecuador/logo-mono.svg +1 -0
- package/dist/assets/avianca-ecuador/logo.svg +1 -0
- package/dist/assets/avianca-el-salvador/icon-mono.svg +1 -0
- package/dist/assets/avianca-el-salvador/icon.svg +1 -0
- package/dist/assets/avianca-el-salvador/logo-mono.svg +1 -0
- package/dist/assets/avianca-el-salvador/logo.svg +1 -0
- package/dist/assets/azul/icon.svg +1 -1
- package/dist/assets/binter/logo-mono.svg +1 -0
- package/dist/assets/binter/logo.svg +1 -0
- package/dist/assets/cebu-pacific/icon.svg +1 -1
- package/dist/assets/cebu-pacific/logo.svg +1 -1
- package/dist/assets/china-airlines/logo.svg +1 -1
- package/dist/assets/china-eastern-airlines/icon.svg +1 -1
- package/dist/assets/china-southern-airlines/tail.svg +1 -1
- package/dist/assets/condor/icon-mono.svg +1 -1
- package/dist/assets/condor/icon.svg +1 -1
- package/dist/assets/condor/logo-mono.svg +1 -1
- package/dist/assets/condor/logo.svg +1 -1
- package/dist/assets/delta-air-lines/icon-mono.svg +1 -1
- package/dist/assets/easyjet/logo-mono.svg +1 -1
- package/dist/assets/easyjet/logo.svg +1 -1
- package/dist/assets/egyptair/icon-mono.svg +1 -1
- package/dist/assets/egyptair/icon.svg +1 -1
- package/dist/assets/flybe/logo-mono.svg +1 -1
- package/dist/assets/flybe/logo.svg +1 -1
- package/dist/assets/frontier-airlines/icon-mono.svg +1 -1
- package/dist/assets/frontier-airlines/icon.svg +1 -1
- package/dist/assets/gol/logo.svg +1 -1
- package/dist/assets/hawaiian-airlines/icon.svg +1 -1
- package/dist/assets/hawaiian-airlines/logo.svg +1 -1
- package/dist/assets/indonesia-airasia/logo-mono.svg +1 -0
- package/dist/assets/indonesia-airasia/logo.svg +1 -0
- package/dist/assets/ita-airways/logo.svg +1 -1
- package/dist/assets/jet2com/icon-mono.svg +1 -1
- package/dist/assets/jet2com/icon.svg +1 -1
- package/dist/assets/jetstar-japan/icon-mono.svg +1 -0
- package/dist/assets/jetstar-japan/icon.svg +1 -0
- package/dist/assets/jetstar-japan/logo-mono.svg +1 -0
- package/dist/assets/jetstar-japan/logo.svg +1 -0
- package/dist/assets/juneyao-air/icon-mono.svg +1 -0
- package/dist/assets/juneyao-air/icon.svg +1 -0
- package/dist/assets/juneyao-air/logo-mono.svg +1 -0
- package/dist/assets/juneyao-air/logo.svg +1 -0
- package/dist/assets/latam-airlines-brasil/icon-mono.svg +1 -0
- package/dist/assets/latam-airlines-brasil/icon.svg +1 -0
- package/dist/assets/latam-airlines-brasil/logo-mono.svg +1 -0
- package/dist/assets/latam-airlines-brasil/logo.svg +1 -0
- package/dist/assets/latam-airlines-colombia/icon-mono.svg +1 -0
- package/dist/assets/latam-airlines-colombia/icon.svg +1 -0
- package/dist/assets/latam-airlines-colombia/logo-mono.svg +1 -0
- package/dist/assets/latam-airlines-colombia/logo.svg +1 -0
- package/dist/assets/latam-airlines-ecuador/icon-mono.svg +1 -0
- package/dist/assets/latam-airlines-ecuador/icon.svg +1 -0
- package/dist/assets/latam-airlines-ecuador/logo-mono.svg +1 -0
- package/dist/assets/latam-airlines-ecuador/logo.svg +1 -0
- package/dist/assets/latam-airlines-paraguay/icon-mono.svg +1 -0
- package/dist/assets/latam-airlines-paraguay/icon.svg +1 -0
- package/dist/assets/latam-airlines-paraguay/logo-mono.svg +1 -0
- package/dist/assets/latam-airlines-paraguay/logo.svg +1 -0
- package/dist/assets/latam-airlines-peru/icon-mono.svg +1 -0
- package/dist/assets/latam-airlines-peru/icon.svg +1 -0
- package/dist/assets/latam-airlines-peru/logo-mono.svg +1 -0
- package/dist/assets/latam-airlines-peru/logo.svg +1 -0
- package/dist/assets/level/icon.svg +1 -0
- package/dist/assets/level/logo.svg +1 -0
- package/dist/assets/lion-air/icon-mono.svg +1 -1
- package/dist/assets/lion-air/icon.svg +1 -1
- package/dist/assets/norwegian-air-shuttle/icon.svg +1 -1
- package/dist/assets/norwegian-air-shuttle/logo-mono.svg +1 -1
- package/dist/assets/norwegian-air-shuttle/logo.svg +1 -1
- package/dist/assets/pegasus-airlines/icon-mono.svg +1 -1
- package/dist/assets/pegasus-airlines/icon.svg +1 -1
- package/dist/assets/philippines-airasia/logo-mono.svg +1 -0
- package/dist/assets/philippines-airasia/logo.svg +1 -0
- package/dist/assets/sas-connect/logo-mono.svg +1 -0
- package/dist/assets/sas-connect/logo.svg +1 -0
- package/dist/assets/spirit-airlines/logo-mono.svg +1 -1
- package/dist/assets/spirit-airlines/logo.svg +1 -1
- package/dist/assets/thai-airasia/logo-mono.svg +1 -0
- package/dist/assets/thai-airasia/logo.svg +1 -0
- package/dist/assets/viva/icon-mono.svg +1 -1
- package/dist/assets/viva/icon.svg +1 -1
- package/dist/assets/volaris/icon.svg +1 -1
- package/dist/assets/vueling/logo.svg +1 -1
- package/dist/assets/winair/logo-mono.svg +1 -0
- package/dist/assets/winair/logo.svg +1 -0
- package/dist/assets/wizz-air-abu-dhabi/logo-mono.svg +1 -0
- package/dist/assets/wizz-air-abu-dhabi/logo.svg +1 -0
- package/dist/assets/wizz-air-uk/logo-mono.svg +1 -0
- package/dist/assets/wizz-air-uk/logo.svg +1 -0
- package/dist/index.d.cts +7 -11
- package/dist/index.d.ts +7 -11
- package/dist/svg-validator.cjs +382 -5
- package/dist/svg-validator.d.cts +12 -0
- package/dist/svg-validator.d.ts +12 -0
- package/dist/svg-validator.js +382 -5
- package/package.json +4 -3
- package/AIRLINES.md +0 -144
package/dist/svg-validator.js
CHANGED
|
@@ -4,6 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
const r = (n) => Math.round(n * 1000) / 1000
|
|
6
6
|
|
|
7
|
+
// Top-level constants are inlined by the build like the functions; only
|
|
8
|
+
// functions may be exported (see scripts/build.js)
|
|
9
|
+
const HEX = /^#(?:[0-9A-F]{3}){1,2}$/i
|
|
10
|
+
// Paint keywords that are not colors to check or move
|
|
11
|
+
const PAINT_KEYWORDS = /^(none|currentColor|transparent|inherit)$/i
|
|
12
|
+
|
|
13
|
+
// Whether a file is a monochrome variant, drawn in currentColor
|
|
14
|
+
function isMonoFile(name) {
|
|
15
|
+
return name.toLowerCase().endsWith('-mono.svg')
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
// Check: <title> exists and has non-empty text content
|
|
8
19
|
function validateTitle(svg) {
|
|
9
20
|
const title = svg.querySelector('title')
|
|
@@ -83,6 +94,17 @@ function validateNoTransforms(svg) {
|
|
|
83
94
|
return { pass, detail }
|
|
84
95
|
}
|
|
85
96
|
|
|
97
|
+
// Check: no <g> inside another <g> (a single level is fine for shared attributes)
|
|
98
|
+
function validateNoNestedGroups(svg) {
|
|
99
|
+
const nested = svg.querySelectorAll('g g').length
|
|
100
|
+
const pass = nested === 0
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
pass,
|
|
104
|
+
detail: pass ? 'clean' : `${nested} nested <g> found`,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
86
108
|
// Check: geometry โ fills viewBox, fits within it, and is centered.
|
|
87
109
|
// Requires a pre-computed bbox (e.g. from getBBox() in the browser).
|
|
88
110
|
// Returns an array of SvgValidation-shaped objects (may be 1โ3 items).
|
|
@@ -151,12 +173,367 @@ function validateGeometry(type, bbox) {
|
|
|
151
173
|
return results
|
|
152
174
|
}
|
|
153
175
|
|
|
176
|
+
// Check: <title> is the first element inside <svg>, so screen readers and
|
|
177
|
+
// the catalog read it before anything else
|
|
178
|
+
function validateTitleFirst(svg) {
|
|
179
|
+
const title = svg.querySelector('title')
|
|
180
|
+
const first = svg.firstElementChild
|
|
181
|
+
const pass = !!title && first === title
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
pass,
|
|
185
|
+
detail: pass
|
|
186
|
+
? 'first child'
|
|
187
|
+
: title
|
|
188
|
+
? `<${first.tagName.toLowerCase()}> comes before <title>`
|
|
189
|
+
: 'missing',
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check: no width or height on the root <svg>; the viewBox sets the size, so
|
|
194
|
+
// the marks scale to whatever box they are placed in
|
|
195
|
+
function validateNoDimensions(svg) {
|
|
196
|
+
const attrs = ['width', 'height'].filter((a) => svg.hasAttribute(a))
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
pass: attrs.length === 0,
|
|
200
|
+
detail: attrs.length
|
|
201
|
+
? attrs.map((a) => `${a}="${svg.getAttribute(a)}"`).join(' ')
|
|
202
|
+
: 'clean',
|
|
203
|
+
attrs,
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Check: no comments, <metadata> or design-tool namespaces and attributes
|
|
208
|
+
// (Inkscape, Sketch, Illustratorโฆ) inside <svg>
|
|
209
|
+
function validateNoEditorData(svg) {
|
|
210
|
+
const editorPrefix = /^(inkscape|sodipodi|sketch|serif|i|x|graph|a):/
|
|
211
|
+
const editorNs = /^xmlns:(?!xlink$)/
|
|
212
|
+
const walker = svg.ownerDocument.createTreeWalker(svg, 128) // comments
|
|
213
|
+
const comments = []
|
|
214
|
+
while (walker.nextNode()) comments.push(walker.currentNode)
|
|
215
|
+
|
|
216
|
+
const leftovers = new Set()
|
|
217
|
+
for (const el of [svg, ...svg.querySelectorAll('*')]) {
|
|
218
|
+
if (
|
|
219
|
+
el.tagName.toLowerCase() === 'metadata' ||
|
|
220
|
+
editorPrefix.test(el.tagName)
|
|
221
|
+
) {
|
|
222
|
+
leftovers.add(`<${el.tagName}>`)
|
|
223
|
+
}
|
|
224
|
+
for (const { name } of el.attributes) {
|
|
225
|
+
if (editorPrefix.test(name) || editorNs.test(name)) {
|
|
226
|
+
leftovers.add(name)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const list = [...leftovers]
|
|
231
|
+
const found = comments.length ? ['comments', ...list] : list
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
pass: found.length === 0,
|
|
235
|
+
detail: found.length ? found.join(', ') : 'clean',
|
|
236
|
+
comments,
|
|
237
|
+
leftovers: list,
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Check: path data is written the way SVGO writes it: at most five decimals,
|
|
242
|
+
// no negative zero and no leading zero (".5", not "0.5"). `paths` lists what
|
|
243
|
+
// each <path> breaks, with `trimmed` the d without leading zeros.
|
|
244
|
+
function validateNumberFormat(svg) {
|
|
245
|
+
const maxDecimals = 5
|
|
246
|
+
const paths = []
|
|
247
|
+
for (const el of svg.querySelectorAll('path[d]')) {
|
|
248
|
+
const d = el.getAttribute('d')
|
|
249
|
+
const long = d.match(new RegExp(`\\.\\d{${maxDecimals + 1},}`))
|
|
250
|
+
const negativeZero = /-0(?![\d.])/.test(d)
|
|
251
|
+
// "0.5" -> ".5" and "-0.5" -> "-.5": the same numbers, written shorter
|
|
252
|
+
const trimmed = d.replace(/(?<![\d.])0\.(?=\d)/g, '.')
|
|
253
|
+
if (long || negativeZero || trimmed !== d) {
|
|
254
|
+
paths.push({
|
|
255
|
+
el,
|
|
256
|
+
long: long?.[0] ?? null,
|
|
257
|
+
negativeZero,
|
|
258
|
+
trimmed: trimmed !== d ? trimmed : null,
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const problems = [
|
|
263
|
+
paths.some((p) => p.long) &&
|
|
264
|
+
`more than ${maxDecimals} decimals ("${paths.find((p) => p.long).long}")`,
|
|
265
|
+
paths.some((p) => p.negativeZero) && 'negative zero',
|
|
266
|
+
paths.some((p) => p.trimmed) && 'leading zeros ("0.5" โ ".5")',
|
|
267
|
+
].filter(Boolean)
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
pass: paths.length === 0,
|
|
271
|
+
detail: problems.length ? problems.join(' ยท ') : 'clean',
|
|
272
|
+
maxDecimals,
|
|
273
|
+
paths,
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// The six-digit lowercase form of a hex color: #F60 -> #ff6600
|
|
278
|
+
function toFullHex(hex) {
|
|
279
|
+
return (
|
|
280
|
+
hex.length === 4
|
|
281
|
+
? '#' + [...hex.slice(1)].map((c) => c + c).join('')
|
|
282
|
+
: hex
|
|
283
|
+
).toLowerCase()
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Check: colors in fill, stroke and stop-color are six-digit lowercase hex.
|
|
287
|
+
// `invalid` holds values that aren't hex at all (color names, rgb()), `short`
|
|
288
|
+
// hex that only needs rewriting.
|
|
289
|
+
function validateHexColors(svg) {
|
|
290
|
+
const invalid = []
|
|
291
|
+
const short = []
|
|
292
|
+
for (const attr of ['fill', 'stroke', 'stop-color']) {
|
|
293
|
+
const els = [svg, ...svg.querySelectorAll(`[${attr}]`)]
|
|
294
|
+
for (const el of els.filter((e) => e.hasAttribute(attr))) {
|
|
295
|
+
const value = el.getAttribute(attr).trim()
|
|
296
|
+
if (PAINT_KEYWORDS.test(value) || /^url\(.*\)$/.test(value))
|
|
297
|
+
continue
|
|
298
|
+
if (!HEX.test(value)) {
|
|
299
|
+
invalid.push({ el, attr, value })
|
|
300
|
+
continue
|
|
301
|
+
}
|
|
302
|
+
const full = toFullHex(value)
|
|
303
|
+
if (value !== full) short.push({ el, attr, value, full })
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const problems = [
|
|
307
|
+
...invalid.map((c) => `${c.attr}="${c.value}" is not hex`),
|
|
308
|
+
...short.map((c) => `"${c.value}" should be "${c.full}"`),
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
pass: problems.length === 0,
|
|
313
|
+
detail: problems.length ? problems.join(' ยท ') : 'clean',
|
|
314
|
+
invalid,
|
|
315
|
+
short,
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Check: a single-color SVG sets its fill once, on the root <svg>
|
|
320
|
+
// (currentColor for a -mono file), and none of its children has one; a
|
|
321
|
+
// multi-color SVG has no fill on the root. Gradients and non-hex fills are
|
|
322
|
+
// left alone (`skipped`): validateHexColors reports the latter.
|
|
323
|
+
// `rootFill` is the fill the root should have (null for none), `rootOk`
|
|
324
|
+
// whether it has it, and `redundant` the children whose fill should go.
|
|
325
|
+
function validateFillPlacement(svg, isMono = false) {
|
|
326
|
+
// The root counts: a single-color SVG keeps its fill there
|
|
327
|
+
const withFill = [svg, ...svg.querySelectorAll('[fill]')].filter((el) =>
|
|
328
|
+
el.hasAttribute('fill'),
|
|
329
|
+
)
|
|
330
|
+
const fills = withFill.map((el) => el.getAttribute('fill').trim())
|
|
331
|
+
const result = (pass, detail, rest) => ({
|
|
332
|
+
pass,
|
|
333
|
+
detail,
|
|
334
|
+
skipped: false,
|
|
335
|
+
rootFill: null,
|
|
336
|
+
rootOk: true,
|
|
337
|
+
redundant: [],
|
|
338
|
+
...rest,
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
if (fills.some((f) => !HEX.test(f) && !PAINT_KEYWORDS.test(f))) {
|
|
342
|
+
return result(true, 'skipped: gradient or non-hex fill', {
|
|
343
|
+
skipped: true,
|
|
344
|
+
})
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const colors = new Set(
|
|
348
|
+
fills.filter((f) => HEX.test(f)).map((f) => f.toLowerCase()),
|
|
349
|
+
)
|
|
350
|
+
const existing = svg.getAttribute('fill')
|
|
351
|
+
|
|
352
|
+
if (colors.size > 1) {
|
|
353
|
+
const rootOk = existing === null
|
|
354
|
+
|
|
355
|
+
return result(
|
|
356
|
+
rootOk,
|
|
357
|
+
rootOk
|
|
358
|
+
? `${colors.size} colors, no root fill`
|
|
359
|
+
: 'root <svg> should have no fill in a multi-color SVG',
|
|
360
|
+
{ rootOk },
|
|
361
|
+
)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const rootFill = isMono ? 'currentColor' : [...colors][0] || 'currentColor'
|
|
365
|
+
const rootOk = isMono
|
|
366
|
+
? existing === rootFill
|
|
367
|
+
: existing?.toLowerCase() === rootFill
|
|
368
|
+
const redundant = withFill.filter((el) => el !== svg)
|
|
369
|
+
const problems = [
|
|
370
|
+
!rootOk && `root <svg> should have fill="${rootFill}"`,
|
|
371
|
+
redundant.length &&
|
|
372
|
+
`remove fill from ${redundant.length} child element${redundant.length > 1 ? 's' : ''}`,
|
|
373
|
+
].filter(Boolean)
|
|
374
|
+
|
|
375
|
+
return result(
|
|
376
|
+
problems.length === 0,
|
|
377
|
+
problems.length ? problems.join(' ยท ') : `fill="${rootFill}"`,
|
|
378
|
+
{ rootFill, rootOk, redundant },
|
|
379
|
+
)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Check (warning): a single-color SVG draws everything in one <path>
|
|
383
|
+
function validateSinglePath(svg, isMono = false) {
|
|
384
|
+
const hexFill = /#[0-9a-f]{3}(?:[0-9a-f]{3})?\b/gi
|
|
385
|
+
const markup = svg.outerHTML
|
|
386
|
+
const usesPaint = /url\(#|stroke=|stroke:|<(clipPath|mask)\b/i.test(markup)
|
|
387
|
+
const fills = new Set(
|
|
388
|
+
[...svg.querySelectorAll('[fill], [style]')].flatMap((el) =>
|
|
389
|
+
(
|
|
390
|
+
`${el.getAttribute('fill') || ''} ${el.getAttribute('style') || ''}`.match(
|
|
391
|
+
hexFill,
|
|
392
|
+
) || []
|
|
393
|
+
).map((c) => c.toLowerCase()),
|
|
394
|
+
),
|
|
395
|
+
)
|
|
396
|
+
if (!isMono && (usesPaint || fills.size > 1)) {
|
|
397
|
+
return { pass: true, detail: 'multi-color', count: 0 }
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const count = [
|
|
401
|
+
...svg.querySelectorAll(
|
|
402
|
+
'path, circle, rect, ellipse, polygon, polyline, line',
|
|
403
|
+
),
|
|
404
|
+
].filter((el) => !el.closest('defs, clipPath, mask')).length
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
pass: count <= 1,
|
|
408
|
+
detail: count <= 1 ? 'one shape' : `${count} shapes`,
|
|
409
|
+
count,
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// The file with anything before <svg> (XML declaration, DOCTYPE, comment)
|
|
414
|
+
// removed
|
|
415
|
+
function stripProlog(source) {
|
|
416
|
+
const start = source.indexOf('<svg')
|
|
417
|
+
|
|
418
|
+
return start > 0 ? source.slice(start) : source
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// The file with fill before d on every <path>
|
|
422
|
+
function orderPathAttrs(source) {
|
|
423
|
+
return source.replace(
|
|
424
|
+
/<path([^>]*?) (d="[^"]*?")([^>]*?) (fill="[^"]*?")/g,
|
|
425
|
+
(match, before, dAttr, after, fillAttr) =>
|
|
426
|
+
`<path${before} ${fillAttr} ${dAttr}${after}`,
|
|
427
|
+
)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// The file with the root <svg> attributes ordered role, viewBox, xmlns, fill,
|
|
431
|
+
// then the rest
|
|
432
|
+
function orderSvgAttrs(source) {
|
|
433
|
+
const first = ['role', 'viewBox', 'xmlns', 'fill']
|
|
434
|
+
const svgTag = source.match(/<svg([^>]*)>/)
|
|
435
|
+
if (!svgTag) return source
|
|
436
|
+
|
|
437
|
+
const attributes = {}
|
|
438
|
+
const attrRegex = /([a-zA-Z0-9:-]+)="([^"]*)"/g
|
|
439
|
+
let match
|
|
440
|
+
while ((match = attrRegex.exec(svgTag[1])) !== null) {
|
|
441
|
+
attributes[match[1]] = match[2]
|
|
442
|
+
}
|
|
443
|
+
const ordered = [
|
|
444
|
+
...first.filter((k) => k in attributes),
|
|
445
|
+
...Object.keys(attributes).filter((k) => !first.includes(k)),
|
|
446
|
+
]
|
|
447
|
+
.map((k) => `${k}="${attributes[k]}"`)
|
|
448
|
+
.join(' ')
|
|
449
|
+
if (ordered.replace(/\s+/g, '') === svgTag[1].trim().replace(/\s+/g, '')) {
|
|
450
|
+
return source
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return source.replace(svgTag[0], `<svg ${ordered}>`)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Check: the file starts with <svg>, with nothing before it
|
|
457
|
+
function validateNoProlog(source) {
|
|
458
|
+
const pass = stripProlog(source) === source
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
pass,
|
|
462
|
+
detail: pass ? 'clean' : 'content before <svg>',
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Check: root <svg> attributes come as role, viewBox, xmlns, fill, then the
|
|
467
|
+
// rest, and fill comes before d on <path>
|
|
468
|
+
function validateAttrOrder(source) {
|
|
469
|
+
const svgOk = orderSvgAttrs(source) === source
|
|
470
|
+
const pathOk = orderPathAttrs(source) === source
|
|
471
|
+
const problems = [
|
|
472
|
+
!svgOk && 'root <svg>: role, viewBox, xmlns, fill first',
|
|
473
|
+
!pathOk && '<path>: fill before d',
|
|
474
|
+
].filter(Boolean)
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
pass: problems.length === 0,
|
|
478
|
+
detail: problems.length ? problems.join(' ยท ') : 'clean',
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
// The same checks as `scripts/lint-svg.js`, in the browser. `fileName` tells
|
|
484
|
+
// a -mono.svg apart (its fill is currentColor); `source`, the file's text,
|
|
485
|
+
// adds the check that needs it (nothing before <svg>) and is what the
|
|
486
|
+
// attribute order is read from, falling back to the parsed element as the
|
|
487
|
+
// CLI does.
|
|
488
|
+
export function runSvgValidations(svg, type, bbox = null, options = {}) {
|
|
489
|
+
const { fileName = '', source = null } = options
|
|
490
|
+
const isMono = isMonoFile(fileName)
|
|
491
|
+
const row = (group, label, result, level = 'error') => ({
|
|
492
|
+
group,
|
|
493
|
+
label,
|
|
494
|
+
level,
|
|
495
|
+
pass: result.pass,
|
|
496
|
+
detail: result.detail,
|
|
497
|
+
})
|
|
154
498
|
|
|
155
|
-
export function runSvgValidations(svg, type, bbox = null) {
|
|
156
499
|
return [
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
500
|
+
row(
|
|
501
|
+
'Structure',
|
|
502
|
+
'Required attributes',
|
|
503
|
+
validateRequiredAttrs(svg, type),
|
|
504
|
+
),
|
|
505
|
+
row('Structure', 'Title first', validateTitleFirst(svg)),
|
|
506
|
+
row('Structure', 'No embedded scripts', validateNoScripts(svg)),
|
|
507
|
+
row('Structure', 'No transforms', validateNoTransforms(svg)),
|
|
508
|
+
row(
|
|
509
|
+
'Structure',
|
|
510
|
+
'No nested groups',
|
|
511
|
+
validateNoNestedGroups(svg),
|
|
512
|
+
'warning',
|
|
513
|
+
),
|
|
514
|
+
row(
|
|
515
|
+
'Structure',
|
|
516
|
+
'Single path',
|
|
517
|
+
validateSinglePath(svg, isMono),
|
|
518
|
+
'warning',
|
|
519
|
+
),
|
|
520
|
+
row('Format', 'No width or height', validateNoDimensions(svg)),
|
|
521
|
+
row('Format', 'No editor data', validateNoEditorData(svg)),
|
|
522
|
+
row('Format', 'Number format', validateNumberFormat(svg)),
|
|
523
|
+
...(source === null
|
|
524
|
+
? []
|
|
525
|
+
: [row('Format', 'Starts with <svg>', validateNoProlog(source))]),
|
|
526
|
+
row(
|
|
527
|
+
'Format',
|
|
528
|
+
'Attribute order',
|
|
529
|
+
validateAttrOrder(source ?? svg.outerHTML),
|
|
530
|
+
),
|
|
531
|
+
row('Style', 'Hex colors', validateHexColors(svg)),
|
|
532
|
+
row('Style', 'Fill placement', validateFillPlacement(svg, isMono)),
|
|
533
|
+
...validateGeometry(type, bbox).map((check) => ({
|
|
534
|
+
group: 'Geometry',
|
|
535
|
+
level: 'error',
|
|
536
|
+
...check,
|
|
537
|
+
})),
|
|
161
538
|
]
|
|
162
539
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "soaring-symbols",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "A curated collection of airline branding assets โ logos, icons, and tail designs in SVG format.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -59,7 +59,6 @@
|
|
|
59
59
|
],
|
|
60
60
|
"files": [
|
|
61
61
|
"dist/",
|
|
62
|
-
"AIRLINES.md",
|
|
63
62
|
"API.md",
|
|
64
63
|
"LICENSE",
|
|
65
64
|
"README.md"
|
|
@@ -76,9 +75,11 @@
|
|
|
76
75
|
"lint": "eslint .",
|
|
77
76
|
"lint:fix": "eslint . --fix",
|
|
78
77
|
"format": "prettier --write \"**/*.{js,ts}\"",
|
|
78
|
+
"format:check": "prettier --check \"**/*.{js,ts}\"",
|
|
79
79
|
"release": "node scripts/release.js",
|
|
80
80
|
"sync:data": "node scripts/sync-data.js",
|
|
81
|
-
"optimize": "svgo --config svgo.config.mjs"
|
|
81
|
+
"optimize": "svgo --config svgo.config.mjs",
|
|
82
|
+
"optimize:check": "node scripts/check-optimized.js"
|
|
82
83
|
},
|
|
83
84
|
"lint-staged": {
|
|
84
85
|
"*.js": [
|
package/AIRLINES.md
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
# Soaring Symbols: Airlines
|
|
2
|
-
|
|
3
|
-
This file provides an overview of the airlines included in the Soaring Symbols project.
|
|
4
|
-
|
|
5
|
-
| Airline | Country | IATA | ICAO | Alliance | Primary Color | Icon | Logo | Tail |
|
|
6
|
-
|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
|
7
|
-
| [Aegean Airlines](https://en.aegeanair.com) ๐ฌ๐ท | GR | A3 | AEE | Star Alliance |  | โ
| โ
| |
|
|
8
|
-
| [Aer Lingus](https://www.aerlingus.com) ๐ฎ๐ช | IE | EI | EIN | |  | โ๏ธ | โ๏ธ | |
|
|
9
|
-
| [Aerolรญneas Argentinas](https://www.aerolineas.com.ar) ๐ฆ๐ท | AR | AR | ARG | SkyTeam |  | โ
| โ
| |
|
|
10
|
-
| [Aeromรฉxico](https://www.aeromexico.com) ๐ฒ๐ฝ | MX | AM | AMX | SkyTeam |  | โ
| โ
| |
|
|
11
|
-
| [Air Albania](https://www.airalbania.com.al) ๐ฆ๐ฑ | AL | ZB | ABN | |  | โ
| โ
| |
|
|
12
|
-
| [Air Algรฉrie](https://airalgerie.dz) ๐ฉ๐ฟ | DZ | AH | DAH | |  | โ
| โ
| |
|
|
13
|
-
| [Air Arabia](https://www.airarabia.com) | AE | G9 | ABY | |  | โ
| โ
| |
|
|
14
|
-
| [Air Astana](https://airastana.com) ๐ฐ๐ฟ | KZ | KC | KZR | |  | โ
| โ
| |
|
|
15
|
-
| [Air Berlin](https://www.airberlin.com) | DE | AB | BER | oneworld |  | โ
| โ
| |
|
|
16
|
-
| [Air Canada](https://www.aircanada.com) ๐จ๐ฆ | CA | AC | ACA | Star Alliance |  | โ
| โ
| |
|
|
17
|
-
| [Air China](https://www.airchina.com) ๐จ๐ณ | CN | CA | CCA | Star Alliance |  | โ
| โ
| |
|
|
18
|
-
| [Air Dolomiti](https://www.airdolomiti.eu) | IT | EN | DLA | |  | โ
| โ
| |
|
|
19
|
-
| [Air Europa](https://www.aireuropa.com) | ES | UX | AEA | SkyTeam |  | โ
| โ
| |
|
|
20
|
-
| [Air France](https://airfrance.com) ๐ซ๐ท | FR | AF | AFR | SkyTeam |  | โ
| โ
| |
|
|
21
|
-
| [Air India](https://www.airindia.com) ๐ฎ๐ณ | IN | AI | AIC | Star Alliance |  | โ
| โ
| |
|
|
22
|
-
| [Air Mauritius](https://www.airmauritius.com) ๐ฒ๐บ | MU | MK | MAU | Vanilla Alliance |  | โ
| โ
| |
|
|
23
|
-
| [Air New Zealand](https://www.airnewzealand.co.nz) ๐ณ๐ฟ | NZ | NZ | ANZ | Star Alliance |  | โ
| โ
| |
|
|
24
|
-
| [Air Serbia](https://www.airserbia.com) ๐ท๐ธ | RS | JU | ASL | |  | โ
| โ
| |
|
|
25
|
-
| [Air Transat](https://airtransat.com) | CA | TS | TSC | |  | โ
| โ
| |
|
|
26
|
-
| [AirAsia](https://www.airasia.com) | MY | AK | AXM | |  | | โ
| |
|
|
27
|
-
| AirAsia Cambodia | KH | KT | KTC | |  | | โ
| |
|
|
28
|
-
| Thai AirAsia | TH | FD | AIQ | |  | | โ
| |
|
|
29
|
-
| Indonesia AirAsia | ID | QZ | AWQ | |  | | โ
| |
|
|
30
|
-
| Philippines AirAsia | PH | Z2 | APG | |  | | โ
| |
|
|
31
|
-
| [airBaltic](https://www.airbaltic.com) ๐ฑ๐ป | LV | BT | BTI | |  | | โ
| |
|
|
32
|
-
| [Akasa Air](https://www.akasaair.com) | IN | QP | AKJ | |  | โ
| โ
| |
|
|
33
|
-
| [Alaska Airlines](https://www.alaskaair.com) | US | AS | ASA | oneworld |  | | โ
| |
|
|
34
|
-
| [All Nippon Airways](https://www.ana.co.jp) | JP | NH | ANA | Star Alliance |  | | โ๏ธ | |
|
|
35
|
-
| [American Airlines](https://www.aa.com) | US | AA | AAL | oneworld |  | โ
| โ
| |
|
|
36
|
-
| [Asiana Airlines](https://flyasiana.com) | KR | OZ | AAR | Star Alliance |  | โ
| โ
| |
|
|
37
|
-
| [Atlantic Airways](https://www.atlanticairways.com) ๐ซ๐ด | FO | RC | FLI | |  | โ
| โ
| |
|
|
38
|
-
| [Austrian Airlines](https://www.austrian.com) ๐ฆ๐น | AT | OS | AUA | Star Alliance |  | โ
| โ๏ธ | |
|
|
39
|
-
| [avianca](https://www.avianca.com) ๐จ๐ด | CO | AV | AVA | Star Alliance |  | โ
| โ
| |
|
|
40
|
-
| Avianca Costa Rica ๐จ๐ท | CR | LR | LRC | |  | โ
| โ
| |
|
|
41
|
-
| Avianca Ecuador | EC | 2K | GLG | |  | โ
| โ
| |
|
|
42
|
-
| Avianca El Salvador ๐ธ๐ป | SV | TA | TAI | |  | โ
| โ
| |
|
|
43
|
-
| [Azerbaijan Airlines](https://azal.az) ๐ฆ๐ฟ | AZ | J2 | AHY | |  | โ
| โ
| |
|
|
44
|
-
| [Azul](https://www.voeazul.com.br) | BR | AD | AZU | |  | โ๏ธ | โ๏ธ | |
|
|
45
|
-
| [Bamboo Airways](https://www.bambooairways.com) | VN | QH | BAV | |  | โ๏ธ | โ๏ธ | |
|
|
46
|
-
| [Bangkok Airways](https://www.bangkokair.com) | TH | PG | BKP | |  | โ
| โ
| |
|
|
47
|
-
| [British Airways](https://www.britishairways.com) ๐ฌ๐ง | GB | BA | BAW | oneworld |  | โ
| โ
| |
|
|
48
|
-
| [Brussels Airlines](https://www.brusselsairlines.com) ๐ง๐ช | BE | SN | BEL | Star Alliance |  | โ
| โ
| |
|
|
49
|
-
| [Cathay Pacific](https://www.cathaypacific.com) ๐ญ๐ฐ | HK | CX | CPA | oneworld |  | โ
| โ
| |
|
|
50
|
-
| [Cebu Pacific](https://www.cebupacificair.com) | PH | 5J | CEB | |  | โ
| โ
| |
|
|
51
|
-
| [China Airlines](https://www.china-airlines.com) ๐น๐ผ | TW | CI | CAL | SkyTeam |  | โ๏ธ | โ๏ธ | |
|
|
52
|
-
| [China Eastern Airlines](https://www.ceair.com) | CN | MU | CES | SkyTeam |  | โ๏ธ | โ๏ธ | |
|
|
53
|
-
| [China Southern Airlines](https://www.csair.com) | CN | CZ | CSN | |  | โ๏ธ | โ
| โ๏ธ |
|
|
54
|
-
| [Condor](https://www.condor.com) | DE | DE | CFG | |  | โ
| โ
| |
|
|
55
|
-
| [Copa Airlines](https://www.copaair.com) ๐ต๐ฆ | PA | CM | CMP | Star Alliance |  | โ
| โ
| |
|
|
56
|
-
| [Delta Air Lines](https://www.delta.com) | US | DL | DAL | SkyTeam |  | โ
| โ
| |
|
|
57
|
-
| [easyJet](https://www.easyjet.com) | GB | U2 | EZY | |  | โ๏ธ | โ
| |
|
|
58
|
-
| [EgyptAir](https://www.egyptair.com) ๐ช๐ฌ | EG | MS | MSR | Star Alliance |  | โ
| โ
| |
|
|
59
|
-
| [Emirates](https://www.emirates.com) ๐ฆ๐ช | AE | EK | UAE | |  | โ
| โ
| |
|
|
60
|
-
| [Ethiopian Airlines](https://www.ethiopianairlines.com) ๐ช๐น | ET | ET | ETH | Star Alliance |  | โ
| โ
| |
|
|
61
|
-
| [Etihad Airways](https://www.etihad.com) ๐ฆ๐ช | AE | EY | ETD | |  | | โ
| |
|
|
62
|
-
| [Eurowings](https://www.eurowings.com) | DE | EW | EWG | |  | โ
| โ
| |
|
|
63
|
-
| [EVA Air](https://www.evaair.com) | TW | BR | EVA | Star Alliance |  | โ๏ธ | โ
| โ๏ธ |
|
|
64
|
-
| [Ewa Air](https://ewa-air.com) | FR | ZD | EWR | |  | | โ
| |
|
|
65
|
-
| [Fiji Airways](https://www.fijiairways.com) ๐ซ๐ฏ | FJ | FJ | FJI | oneworld |  | โ
| โ
| |
|
|
66
|
-
| [Finnair](https://www.finnair.com) ๐ซ๐ฎ | FI | AY | FIN | oneworld |  | โ
| โ
| |
|
|
67
|
-
| [Firefly](https://www.fireflyz.com.my) | MY | FY | FFM | |  | โ
| โ
| |
|
|
68
|
-
| [Flybe](https://www.flybe.com) | GB | BE | BEE | |  | | โ
| |
|
|
69
|
-
| [flydubai](https://www.flydubai.com) | AE | FZ | FDB | |  | | โ๏ธ | |
|
|
70
|
-
| [flynas](https://www.flynas.com) | SA | XY | KNE | |  | โ
| โ
| |
|
|
71
|
-
| [Frontier Airlines](https://www.flyfrontier.com) | US | F9 | FFT | |  | โ
| โ
| |
|
|
72
|
-
| [Garuda Indonesia](https://www.garuda-indonesia.com) ๐ฎ๐ฉ | ID | GA | GIA | SkyTeam |  | โ
| โ
| |
|
|
73
|
-
| [GOL](https://www.voegol.com.br) | BR | G3 | GLO | |  | โ๏ธ | โ๏ธ | |
|
|
74
|
-
| [Hainan Airlines](https://www.hainanairlines.com) | CN | HU | CHH | |  | โ
| โ
| |
|
|
75
|
-
| [Hawaiian Airlines](https://www.hawaiianairlines.com) | US | HA | HAL | oneworld |  | โ๏ธ | โ๏ธ | |
|
|
76
|
-
| [HK Express](https://www.hkexpress.com) | HK | UO | HKE | |  | โ
| โ
| |
|
|
77
|
-
| [Iberia](https://www.iberia.com) ๐ช๐ธ | ES | IB | IBE | oneworld |  | โ
| โ
| |
|
|
78
|
-
| [Icelandair](https://www.icelandair.com) ๐ฎ๐ธ | IS | FI | ICE | |  | โ
| โ
| โ
|
|
|
79
|
-
| [IndiGo](https://www.goindigo.in) | IN | 6E | IGO | |  | โ
| โ
| |
|
|
80
|
-
| [ITA Airways](https://www.ita-airways.com) ๐ฎ๐น | IT | AZ | ITY | Star Alliance |  | | โ๏ธ | |
|
|
81
|
-
| [Japan Airlines](https://www.jal.co.jp) ๐ฏ๐ต | JP | JL | JAL | oneworld |  | โ
| โ
| |
|
|
82
|
-
| [Jet2.com](https://www.jet2.com) | GB | LS | EXS | |  | โ
| โ
| |
|
|
83
|
-
| [JetBlue Airways](https://www.jetblue.com) | US | B6 | JBU | |  | | โ
| |
|
|
84
|
-
| [Jetstar](https://www.jetstar.com) | AU | JQ | JST | |  | โ
| โ
| |
|
|
85
|
-
| Jetstar Japan | JP | GK | JJP | |  | โ
| โ
| |
|
|
86
|
-
| [Kenya Airways](https://www.kenya-airways.com) ๐ฐ๐ช | KE | KQ | KQA | SkyTeam |  | โ
| โ
| |
|
|
87
|
-
| [KLM](https://www.klm.com) ๐ณ๐ฑ | NL | KL | KLM | SkyTeam |  | โ
| โ
| |
|
|
88
|
-
| [Korean Air](https://www.koreanair.com) ๐ฐ๐ท | KR | KE | KAL | |  | โ
| โ
| |
|
|
89
|
-
| [Kuwait Airways](https://www.kuwaitairways.com) ๐ฐ๐ผ | KW | KU | KAC | |  | โ
| โ
| |
|
|
90
|
-
| [LATAM Airlines](https://www.latamairlines.com) | CL | LA | LAN | |  | โ
| โ
| |
|
|
91
|
-
| LATAM Airlines Brasil | BR | JJ | TAM | |  | โ
| โ
| |
|
|
92
|
-
| LATAM Airlines Colombia | CO | 4C | ARE | |  | โ
| โ
| |
|
|
93
|
-
| LATAM Airlines Ecuador | EC | XL | LNE | |  | โ
| โ
| |
|
|
94
|
-
| LATAM Airlines Perรบ | PE | LP | LPE | |  | โ
| โ
| |
|
|
95
|
-
| LATAM Airlines Paraguay | PY | PZ | LAP | |  | โ
| โ
| |
|
|
96
|
-
| [Lion Air](https://www.lionair.co.id) | ID | JT | LNI | |  | โ
| โ
| |
|
|
97
|
-
| [LOT Polish Airlines](https://www.lot.com) ๐ต๐ฑ | PL | LO | LOT | Star Alliance |  | โ
| โ
| |
|
|
98
|
-
| [Lufthansa](https://www.lufthansa.com) ๐ฉ๐ช | DE | LH | DLH | Star Alliance |  | โ
| โ
| |
|
|
99
|
-
| [Malaysia Airlines](https://www.malaysiaairlines.com) ๐ฒ๐พ | MY | MH | MAS | oneworld |  | โ
| โ
| |
|
|
100
|
-
| [Myanmar National Airlines](https://www.flymna.com) ๐ฒ๐ฒ | MM | UB | UBA | |  | โ
| โ
| |
|
|
101
|
-
| [Norwegian Air Shuttle](https://www.norwegian.com) | NO | DY | NOZ | |  | โ๏ธ | โ
| |
|
|
102
|
-
| [Oman Air](https://www.omanair.com) ๐ด๐ฒ | OM | WY | OMA | oneworld |  | โ
| โ
| |
|
|
103
|
-
| [Pacific Airlines](https://www.pacificairlines.com) | VN | BL | PIC | |  | โ
| โ
| |
|
|
104
|
-
| [Paranair](https://www.paranair.com) | PY | ZP | AZP | |  | โ
| โ
| |
|
|
105
|
-
| [Peach Aviation](https://www.flypeach.com) | JP | MM | APJ | |  | | โ
| |
|
|
106
|
-
| [Pegasus Airlines](https://www.flypgs.com) | TR | PC | PGT | |  | โ
| โ
| |
|
|
107
|
-
| [Philippine Airlines](https://www.philippineairlines.com) ๐ต๐ญ | PH | PR | PAL | |  | โ
| โ
| |
|
|
108
|
-
| [Qantas](https://www.qantas.com) ๐ฆ๐บ | AU | QF | QFA | oneworld |  | โ๏ธ | โ
| |
|
|
109
|
-
| [Qatar Airways](https://www.qatarairways.com) ๐ถ๐ฆ | QA | QR | QTR | oneworld |  | โ
| โ
| |
|
|
110
|
-
| [Riyadh Air](https://www.riyadhair.com) ๐ธ๐ฆ | SA | RX | RXI | |  | โ
| โ
| |
|
|
111
|
-
| [Royal Air Maroc](https://www.royalairmaroc.com) ๐ฒ๐ฆ | MA | AT | RAM | SkyTeam |  | โ
| โ
| โ๏ธ |
|
|
112
|
-
| [Royal Brunei Airlines](https://www.flyroyalbrunei.com) | BN | BI | RBA | |  | | โ๏ธ | |
|
|
113
|
-
| [Ryanair](https://www.ryanair.com) | IE | FR | RYR | |  | โ
| โ
| |
|
|
114
|
-
| [Saudia](https://www.saudia.com) ๐ธ๐ฆ | SA | SV | SVA | SkyTeam |  | โ
| โ
| |
|
|
115
|
-
| [Scandinavian Airlines](https://www.flysas.com) ๐ฉ๐ฐ ๐ณ๐ด ๐ธ๐ช | DK,NO,SE | SK | SAS | Star Alliance |  | | โ
| |
|
|
116
|
-
| SAS Connect | IE | SL | SZS | |  | | โ
| |
|
|
117
|
-
| [Scoot](https://www.flyscoot.com) | SG | TR | TGW | Value Alliance |  | | โ๏ธ | |
|
|
118
|
-
| [Singapore Airlines](https://www.singaporeair.com) ๐ธ๐ฌ | SG | SQ | SIA | Star Alliance |  | โ
| โ
| |
|
|
119
|
-
| [Southwest Airlines](https://www.southwest.com) | US | WN | SWA | |  | โ๏ธ | โ๏ธ | |
|
|
120
|
-
| [Spirit Airlines](https://www.spirit.com) | US | NK | NKS | |  | | โ
| |
|
|
121
|
-
| [Starlux Airlines](https://www.starlux-airlines.com) | TW | JX | SJX | |  | โ
| โ
| |
|
|
122
|
-
| [Sun PhuQuoc Airways](https://www.sunphuquocairways.com) | VN | 9G | SPQ | |  | โ
| โ
| |
|
|
123
|
-
| [SWISS](https://www.swiss.com) ๐จ๐ญ | CH | LX | SWR | Star Alliance |  | โ
| โ
| |
|
|
124
|
-
| [T'way Air](https://www.twayair.com) | KR | TW | TWB | |  | | โ
| |
|
|
125
|
-
| [TAP Air Portugal](https://www.tapairportugal.com) ๐ต๐น | PT | TP | TAP | Star Alliance |  | โ๏ธ | โ๏ธ | |
|
|
126
|
-
| [TAROM](https://www.tarom.ro) ๐ท๐ด | RO | RO | ROT | SkyTeam |  | โ
| โ
| โ
|
|
|
127
|
-
| [Thai Airways](https://www.thaiairways.com) ๐น๐ญ | TH | TG | THA | Star Alliance |  | โ๏ธ | โ
| |
|
|
128
|
-
| [Transavia](https://www.transavia.com) | NL | HV | TRA | |  | โ
| โ
| |
|
|
129
|
-
| [Turkish Airlines](https://www.turkishairlines.com) ๐น๐ท | TR | TK | THY | Star Alliance |  | โ
| โ
| |
|
|
130
|
-
| [United Airlines](https://www.united.com) | US | UA | UAL | Star Alliance |  | โ
| โ
| |
|
|
131
|
-
| [VietJet Air](https://www.vietjetair.com) | VN | VJ | VJC | |  | | โ
| |
|
|
132
|
-
| [Vietnam Airlines](https://www.vietnamairlines.com) ๐ป๐ณ | VN | VN | HVN | SkyTeam |  | โ
| โ
| |
|
|
133
|
-
| [Vietravel Airlines](https://www.vietravelairlines.com) | VN | VU | VAG | |  | โ
| โ
| |
|
|
134
|
-
| [Virgin Atlantic](https://www.virginatlantic.com) | GB | VS | VIR | SkyTeam |  | โ
| โ
| |
|
|
135
|
-
| [Virgin Australia](https://www.virginaustralia.com) | AU | VA | VOZ | |  | โ
| โ
| |
|
|
136
|
-
| [Viva](https://www.vivaaerobus.com) | MX | VB | VIV | |  | โ
| โ
| |
|
|
137
|
-
| [Volaris](https://www.volaris.com) | MX | Y4 | VOI | |  | โ๏ธ | โ๏ธ | |
|
|
138
|
-
| [Volotea](https://www.volotea.com) | ES | V7 | VOE | |  | โ
| โ
| |
|
|
139
|
-
| [Vueling](https://www.vueling.com) | ES | VY | VLG | |  | | โ๏ธ | |
|
|
140
|
-
| [WestJet](https://www.westjet.com) | CA | WS | WJA | |  | โ
| โ
| |
|
|
141
|
-
| [Wizz Air](https://www.wizzair.com) | HU | W6 | WZZ | |  | | โ
| |
|
|
142
|
-
| Wizz Air Abu Dhabi | AE | 5W | WAZ | |  | | โ
| |
|
|
143
|
-
| Wizz Air UK | GB | W9 | WUK | |  | | โ
| |
|
|
144
|
-
| [XiamenAir](https://www.xiamenair.com) | CN | MF | CXA | SkyTeam |  | โ
| โ
| |
|