soaring-symbols 0.2.2 → 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/README.md +6 -5
- 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/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/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/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/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/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/spirit-airlines/logo-mono.svg +1 -1
- package/dist/assets/spirit-airlines/logo.svg +1 -1
- 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/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](https://www.airasia.com) | KH | KT | KTC | |  | | ✅ | |
|
|
28
|
-
| [airBaltic](https://www.airbaltic.com) 🇱🇻 | LV | BT | BTI | |  | | ✅ | |
|
|
29
|
-
| [Akasa Air](https://www.akasaair.com) | IN | QP | AKJ | |  | ✅ | ✅ | |
|
|
30
|
-
| [Alaska Airlines](https://www.alaskaair.com) | US | AS | ASA | oneworld |  | | ✅ | |
|
|
31
|
-
| [All Nippon Airways](https://www.ana.co.jp) | JP | NH | ANA | Star Alliance |  | | ☑️ | |
|
|
32
|
-
| [American Airlines](https://www.aa.com) | US | AA | AAL | oneworld |  | ✅ | ✅ | |
|
|
33
|
-
| [Asiana Airlines](https://flyasiana.com) | KR | OZ | AAR | Star Alliance |  | ✅ | ✅ | |
|
|
34
|
-
| [Atlantic Airways](https://www.atlanticairways.com) 🇫🇴 | FO | RC | FLI | |  | ✅ | ✅ | |
|
|
35
|
-
| [Austrian Airlines](https://www.austrian.com) 🇦🇹 | AT | OS | AUA | Star Alliance |  | ✅ | ☑️ | |
|
|
36
|
-
| [avianca](https://www.avianca.com) 🇨🇴 | CO | AV | AVA | Star Alliance |  | ✅ | ✅ | |
|
|
37
|
-
| [Avianca Costa Rica](https://www.avianca.com) 🇨🇷 | CR | LR | LRC | |  | ✅ | ✅ | |
|
|
38
|
-
| [Avianca Ecuador](https://www.avianca.com) | EC | 2K | GLG | |  | ✅ | ✅ | |
|
|
39
|
-
| [Avianca El Salvador](https://www.avianca.com) 🇸🇻 | SV | TA | TAI | |  | ✅ | ✅ | |
|
|
40
|
-
| [Azerbaijan Airlines](https://azal.az) 🇦🇿 | AZ | J2 | AHY | |  | ✅ | ✅ | |
|
|
41
|
-
| [Azul](https://www.voeazul.com.br) | BR | AD | AZU | |  | ☑️ | ☑️ | |
|
|
42
|
-
| [Bamboo Airways](https://www.bambooairways.com) | VN | QH | BAV | |  | ☑️ | ☑️ | |
|
|
43
|
-
| [Bangkok Airways](https://www.bangkokair.com) | TH | PG | BKP | |  | ✅ | ✅ | |
|
|
44
|
-
| [British Airways](https://www.britishairways.com) 🇬🇧 | GB | BA | BAW | oneworld |  | ✅ | ✅ | |
|
|
45
|
-
| [Brussels Airlines](https://www.brusselsairlines.com) 🇧🇪 | BE | SN | BEL | Star Alliance |  | ✅ | ✅ | |
|
|
46
|
-
| [Cathay Pacific](https://www.cathaypacific.com) 🇭🇰 | HK | CX | CPA | oneworld |  | ✅ | ✅ | |
|
|
47
|
-
| [Cebu Pacific](https://www.cebupacificair.com) | PH | 5J | CEB | |  | ✅ | ✅ | |
|
|
48
|
-
| [China Airlines](https://www.china-airlines.com) 🇹🇼 | TW | CI | CAL | SkyTeam |  | ☑️ | ☑️ | |
|
|
49
|
-
| [China Eastern Airlines](https://www.ceair.com) | CN | MU | CES | SkyTeam |  | ☑️ | ☑️ | |
|
|
50
|
-
| [China Southern Airlines](https://www.csair.com) | CN | CZ | CSN | |  | ☑️ | ✅ | ☑️ |
|
|
51
|
-
| [Condor](https://www.condor.com) | DE | DE | CFG | |  | ✅ | ✅ | |
|
|
52
|
-
| [Copa Airlines](https://www.copaair.com) 🇵🇦 | PA | CM | CMP | Star Alliance |  | ✅ | ✅ | |
|
|
53
|
-
| [Delta Air Lines](https://www.delta.com) | US | DL | DAL | SkyTeam |  | ✅ | ✅ | |
|
|
54
|
-
| [easyJet](https://www.easyjet.com) | GB | U2 | EZY | |  | ☑️ | ✅ | |
|
|
55
|
-
| [EgyptAir](https://www.egyptair.com) 🇪🇬 | EG | MS | MSR | Star Alliance |  | ✅ | ✅ | |
|
|
56
|
-
| [Emirates](https://www.emirates.com) 🇦🇪 | AE | EK | UAE | |  | ✅ | ✅ | |
|
|
57
|
-
| [Ethiopian Airlines](https://www.ethiopianairlines.com) 🇪🇹 | ET | ET | ETH | Star Alliance |  | ✅ | ✅ | |
|
|
58
|
-
| [Etihad Airways](https://www.etihad.com) 🇦🇪 | AE | EY | ETD | |  | | ✅ | |
|
|
59
|
-
| [Eurowings](https://www.eurowings.com) | DE | EW | EWG | |  | ✅ | ✅ | |
|
|
60
|
-
| [EVA Air](https://www.evaair.com) | TW | BR | EVA | Star Alliance |  | ☑️ | ✅ | ☑️ |
|
|
61
|
-
| [Ewa Air](https://ewa-air.com) | FR | ZD | EWR | |  | | ✅ | |
|
|
62
|
-
| [Fiji Airways](https://www.fijiairways.com) 🇫🇯 | FJ | FJ | FJI | oneworld |  | ✅ | ✅ | |
|
|
63
|
-
| [Finnair](https://www.finnair.com) 🇫🇮 | FI | AY | FIN | oneworld |  | ✅ | ✅ | |
|
|
64
|
-
| [Firefly](https://www.fireflyz.com.my) | MY | FY | FFM | |  | ✅ | ✅ | |
|
|
65
|
-
| [Flybe](https://www.flybe.com) | GB | BE | BEE | |  | | ✅ | |
|
|
66
|
-
| [flydubai](https://www.flydubai.com) | AE | FZ | FDB | |  | | ☑️ | |
|
|
67
|
-
| [flynas](https://www.flynas.com) | SA | XY | KNE | |  | ✅ | ✅ | |
|
|
68
|
-
| [Frontier Airlines](https://www.flyfrontier.com) | US | F9 | FFT | |  | ✅ | ✅ | |
|
|
69
|
-
| [Garuda Indonesia](https://www.garuda-indonesia.com) 🇮🇩 | ID | GA | GIA | SkyTeam |  | ✅ | ✅ | |
|
|
70
|
-
| [GOL](https://www.voegol.com.br) | BR | G3 | GLO | |  | ☑️ | ☑️ | |
|
|
71
|
-
| [Hainan Airlines](https://www.hainanairlines.com) | CN | HU | CHH | |  | ✅ | ✅ | |
|
|
72
|
-
| [Hawaiian Airlines](https://www.hawaiianairlines.com) | US | HA | HAL | oneworld |  | ☑️ | ☑️ | |
|
|
73
|
-
| [HK Express](https://www.hkexpress.com) | HK | UO | HKE | |  | ✅ | ✅ | |
|
|
74
|
-
| [Iberia](https://www.iberia.com) 🇪🇸 | ES | IB | IBE | oneworld |  | ✅ | ✅ | |
|
|
75
|
-
| [Icelandair](https://www.icelandair.com) 🇮🇸 | IS | FI | ICE | |  | ✅ | ✅ | ✅ |
|
|
76
|
-
| [IndiGo](https://www.goindigo.in) | IN | 6E | IGO | |  | ✅ | ✅ | |
|
|
77
|
-
| [Indonesia AirAsia](https://www.airasia.com) | ID | QZ | AWQ | |  | | ✅ | |
|
|
78
|
-
| [ITA Airways](https://www.ita-airways.com) 🇮🇹 | IT | AZ | ITY | Star Alliance |  | | ☑️ | |
|
|
79
|
-
| [Japan Airlines](https://www.jal.co.jp) 🇯🇵 | JP | JL | JAL | oneworld |  | ✅ | ✅ | |
|
|
80
|
-
| [Jet2.com](https://www.jet2.com) | GB | LS | EXS | |  | ✅ | ✅ | |
|
|
81
|
-
| [JetBlue Airways](https://www.jetblue.com) | US | B6 | JBU | |  | | ✅ | |
|
|
82
|
-
| [Jetstar](https://www.jetstar.com) | AU | JQ | JST | |  | ✅ | ✅ | |
|
|
83
|
-
| [Jetstar Japan](https://www.jetstar.com/jp/en/home) | JP | GK | JJP | |  | ✅ | ✅ | |
|
|
84
|
-
| [Kenya Airways](https://www.kenya-airways.com) 🇰🇪 | KE | KQ | KQA | SkyTeam |  | ✅ | ✅ | |
|
|
85
|
-
| [KLM](https://www.klm.com) 🇳🇱 | NL | KL | KLM | SkyTeam |  | ✅ | ✅ | |
|
|
86
|
-
| [Korean Air](https://www.koreanair.com) 🇰🇷 | KR | KE | KAL | |  | ✅ | ✅ | |
|
|
87
|
-
| [Kuwait Airways](https://www.kuwaitairways.com) 🇰🇼 | KW | KU | KAC | |  | ✅ | ✅ | |
|
|
88
|
-
| [LATAM Airlines](https://www.latamairlines.com) | CL | LA | LAN | |  | ✅ | ✅ | |
|
|
89
|
-
| [LATAM Airlines Brasil](https://www.latam.com) | BR | JJ | TAM | |  | ✅ | ✅ | |
|
|
90
|
-
| [LATAM Airlines Colombia](https://www.latam.com/es_co) | CO | 4C | ARE | |  | ✅ | ✅ | |
|
|
91
|
-
| [LATAM Airlines Ecuador](https://www.latamairlines.com) | EC | XL | LNE | |  | ✅ | ✅ | |
|
|
92
|
-
| [LATAM Airlines Paraguay](https://www.latam.com/es_py) | PY | PZ | LAP | |  | ✅ | ✅ | |
|
|
93
|
-
| [LATAM Airlines Perú](https://www.latam.com) | PE | LP | LPE | |  | ✅ | ✅ | |
|
|
94
|
-
| [Lion Air](https://www.lionair.co.id) | ID | JT | LNI | |  | ✅ | ✅ | |
|
|
95
|
-
| [LOT Polish Airlines](https://www.lot.com) 🇵🇱 | PL | LO | LOT | Star Alliance |  | ✅ | ✅ | |
|
|
96
|
-
| [Lufthansa](https://www.lufthansa.com) 🇩🇪 | DE | LH | DLH | Star Alliance |  | ✅ | ✅ | |
|
|
97
|
-
| [Malaysia Airlines](https://www.malaysiaairlines.com) 🇲🇾 | MY | MH | MAS | oneworld |  | ✅ | ✅ | |
|
|
98
|
-
| [Myanmar National Airlines](https://www.flymna.com) 🇲🇲 | MM | UB | UBA | |  | ✅ | ✅ | |
|
|
99
|
-
| [Norwegian Air Shuttle](https://www.norwegian.com) | NO | DY | NOZ | |  | ☑️ | ✅ | |
|
|
100
|
-
| [Oman Air](https://www.omanair.com) 🇴🇲 | OM | WY | OMA | oneworld |  | ✅ | ✅ | |
|
|
101
|
-
| [Pacific Airlines](https://www.pacificairlines.com) | VN | BL | PIC | |  | ✅ | ✅ | |
|
|
102
|
-
| [Paranair](https://www.paranair.com) | PY | ZP | AZP | |  | ✅ | ✅ | |
|
|
103
|
-
| [Peach Aviation](https://www.flypeach.com) | JP | MM | APJ | |  | | ✅ | |
|
|
104
|
-
| [Pegasus Airlines](https://www.flypgs.com) | TR | PC | PGT | |  | ✅ | ✅ | |
|
|
105
|
-
| [Philippine Airlines](https://www.philippineairlines.com) 🇵🇭 | PH | PR | PAL | |  | ✅ | ✅ | |
|
|
106
|
-
| [Philippines AirAsia](https://www.airasia.com) | PH | Z2 | APG | |  | | ✅ | |
|
|
107
|
-
| [Qantas](https://www.qantas.com) 🇦🇺 | AU | QF | QFA | oneworld |  | ☑️ | ✅ | |
|
|
108
|
-
| [Qatar Airways](https://www.qatarairways.com) 🇶🇦 | QA | QR | QTR | oneworld |  | ✅ | ✅ | |
|
|
109
|
-
| [Riyadh Air](https://www.riyadhair.com) 🇸🇦 | SA | RX | RXI | |  | ✅ | ✅ | |
|
|
110
|
-
| [Royal Air Maroc](https://www.royalairmaroc.com) 🇲🇦 | MA | AT | RAM | SkyTeam |  | ✅ | ✅ | ☑️ |
|
|
111
|
-
| [Royal Brunei Airlines](https://www.flyroyalbrunei.com) | BN | BI | RBA | |  | | ☑️ | |
|
|
112
|
-
| [Ryanair](https://www.ryanair.com) | IE | FR | RYR | |  | ✅ | ✅ | |
|
|
113
|
-
| [SAS Connect](https://www.flysas.com) | IE | SL | SZS | |  | | ✅ | |
|
|
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
|
-
| [Scoot](https://www.flyscoot.com) | SG | TR | TGW | Value Alliance |  | | ☑️ | |
|
|
117
|
-
| [Singapore Airlines](https://www.singaporeair.com) 🇸🇬 | SG | SQ | SIA | Star Alliance |  | ✅ | ✅ | |
|
|
118
|
-
| [Southwest Airlines](https://www.southwest.com) | US | WN | SWA | |  | ☑️ | ☑️ | |
|
|
119
|
-
| [Spirit Airlines](https://www.spirit.com) | US | NK | NKS | |  | | ✅ | |
|
|
120
|
-
| [Starlux Airlines](https://www.starlux-airlines.com) | TW | JX | SJX | |  | ✅ | ✅ | |
|
|
121
|
-
| [Sun PhuQuoc Airways](https://www.sunphuquocairways.com) | VN | 9G | SPQ | |  | ✅ | ✅ | |
|
|
122
|
-
| [SWISS](https://www.swiss.com) 🇨🇭 | CH | LX | SWR | Star Alliance |  | ✅ | ✅ | |
|
|
123
|
-
| [T'way Air](https://www.twayair.com) | KR | TW | TWB | |  | | ✅ | |
|
|
124
|
-
| [TAP Air Portugal](https://www.tapairportugal.com) 🇵🇹 | PT | TP | TAP | Star Alliance |  | ☑️ | ☑️ | |
|
|
125
|
-
| [TAROM](https://www.tarom.ro) 🇷🇴 | RO | RO | ROT | SkyTeam |  | ✅ | ✅ | ✅ |
|
|
126
|
-
| [Thai AirAsia](https://www.airasia.com) | TH | FD | AIQ | |  | | ✅ | |
|
|
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](https://www.wizzair.com) | AE | 5W | WAZ | |  | | ✅ | |
|
|
143
|
-
| [Wizz Air UK](https://www.wizzair.com) | GB | W9 | WUK | |  | | ✅ | |
|
|
144
|
-
| [XiamenAir](https://www.xiamenair.com) | CN | MF | CXA | SkyTeam |  | ✅ | ✅ | |
|