Compare commits

..

No commits in common. "demo" and "v19.0.0" have entirely different histories.

1026 changed files with 61040 additions and 62089 deletions

95
.eslintrc.json Normal file
View File

@ -0,0 +1,95 @@
{
"root": true,
"env": {
"es6": true
},
"parserOptions": {
"ecmaVersion": 2018
},
"ignorePatterns": [
"projects/**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"parserOptions": {
"project": [
"tsconfig.json"
],
"createDefaultProgram": true
},
"extends": [
"plugin:@angular-eslint/ng-cli-compat",
"plugin:@angular-eslint/ng-cli-compat--formatting-add-on",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "",
"style": "kebab-case"
}
],
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "",
"style": "camelCase"
}
],
"@typescript-eslint/dot-notation": "off",
"@typescript-eslint/explicit-function-return-type": "error",
"@typescript-eslint/explicit-member-accessibility": [
"off",
{
"accessibility": "explicit"
}
],
"@typescript-eslint/no-inferrable-types": "off",
"arrow-parens": [
"error",
"as-needed",
{
"requireForBlockBody": true
}
],
"brace-style": [
"off",
"off"
],
"import/order": "off",
"max-len": [
"error",
{
"ignorePattern": "^import |^export | implements",
"code": 180
}
],
"no-underscore-dangle": "off",
"object-shorthand": "off",
"quote-props": [
"error",
"consistent"
],
"quotes": [
"error",
"single"
]
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@angular-eslint/template/recommended"
],
"rules": {}
}
]
}

View File

@ -1,11 +0,0 @@
{
"printWidth": 80,
"semi": true,
"bracketSameLine": false,
"trailingComma": "es5",
"tabWidth": 4,
"singleQuote": true,
"bracketSpacing": true,
"arrowParens": "always",
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"]
}

View File

@ -1,6 +1,6 @@
Envato Standard License Envato Standard License
Copyright (c) Withinpixels <hi@withinpixels.com> Copyright (c) Sercan Yemen <sercanyemen@gmail.com>
This project is protected by Envato's Standard License. For more information, This project is protected by Envato's Standard License. For more information,
check the official license page at [https://themeforest.net/licenses/standard](https://themeforest.net/licenses/standard) check the official license page at [https://themeforest.net/licenses/standard](https://themeforest.net/licenses/standard)

View File

@ -20,7 +20,7 @@ Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.
## Running end-to-end tests ## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help ## Further help

View File

@ -15,26 +15,29 @@
"prefix": "app", "prefix": "app",
"architect": { "architect": {
"build": { "build": {
"builder": "@angular-devkit/build-angular:application", "builder": "@angular-devkit/build-angular:browser",
"options": { "options": {
"outputPath": "dist/fuse", "outputPath": "dist/fuse",
"index": "src/index.html", "index": "src/index.html",
"browser": "src/main.ts", "main": "src/main.ts",
"polyfills": ["zone.js"], "polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss", "inlineStyleLanguage": "scss",
"allowedCommonJsDependencies": [ "allowedCommonJsDependencies": [
"apexcharts", "apexcharts",
"highlight.js",
"crypto-js/enc-utf8", "crypto-js/enc-utf8",
"crypto-js/hmac-sha256", "crypto-js/hmac-sha256",
"crypto-js/enc-base64", "crypto-js/enc-base64",
"quill-delta" "flat",
"quill"
], ],
"assets": [ "assets": [
{ "src/favicon-16x16.png",
"glob": "**/*", "src/favicon-32x32.png",
"input": "public" "src/assets",
},
{ {
"glob": "_redirects", "glob": "_redirects",
"input": "src", "input": "src",
@ -42,11 +45,11 @@
} }
], ],
"stylePreprocessorOptions": { "stylePreprocessorOptions": {
"includePaths": ["src/@fuse/styles"] "includePaths": [
"src/@fuse/styles"
]
}, },
"styles": [ "styles": [
"src/styles/splash-screen.css",
"src/styles/inter.css",
"src/@fuse/styles/tailwind.scss", "src/@fuse/styles/tailwind.scss",
"src/@fuse/styles/themes.scss", "src/@fuse/styles/themes.scss",
"src/styles/vendors.scss", "src/styles/vendors.scss",
@ -73,9 +76,12 @@
"outputHashing": "all" "outputHashing": "all"
}, },
"development": { "development": {
"buildOptimizer": false,
"optimization": false, "optimization": false,
"vendorChunk": true,
"extractLicenses": false, "extractLicenses": false,
"sourceMap": true "sourceMap": true,
"namedChunks": true
} }
}, },
"defaultConfiguration": "production" "defaultConfiguration": "production"
@ -84,30 +90,37 @@
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"configurations": { "configurations": {
"production": { "production": {
"buildTarget": "fuse:build:production" "buildTarget": "fuse:build:production"
}, },
"development": { "development": {
"buildTarget": "fuse:build:development" "buildTarget": "fuse:build:development"
} }
}, },
"defaultConfiguration": "development" "defaultConfiguration": "development"
}, },
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n" "builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "fuse:build"
}
}, },
"test": { "test": {
"builder": "@angular-devkit/build-angular:karma", "builder": "@angular-devkit/build-angular:karma",
"options": { "options": {
"polyfills": ["zone.js", "zone.js/testing"], "polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json", "tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss", "inlineStyleLanguage": "scss",
"assets": [ "assets": [
{ "src/favicon-16x16.png",
"glob": "**/*", "src/favicon-32x32.png",
"input": "public" "src/assets"
} ],
"styles": [
"src/styles/styles.scss"
], ],
"styles": ["src/styles/styles.scss"],
"scripts": [] "scripts": []
} }
} }

23570
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "fuse-angular", "name": "fuse-angular",
"version": "21.0.0", "version": "19.0.0",
"description": "Fuse - Angular Admin Template and Starter Project", "description": "Fuse - Angular Admin Template and Starter Project",
"author": "https://themeforest.net/user/srcn", "author": "https://themeforest.net/user/srcn",
"license": "https://themeforest.net/licenses/standard", "license": "https://themeforest.net/licenses/standard",
@ -13,57 +13,54 @@
"test": "ng test" "test": "ng test"
}, },
"dependencies": { "dependencies": {
"@angular/animations": "19.0.5", "@angular/animations": "17.0.3",
"@angular/cdk": "19.0.4", "@angular/cdk": "17.0.1",
"@angular/common": "19.0.5", "@angular/common": "17.0.3",
"@angular/compiler": "19.0.5", "@angular/compiler": "17.0.3",
"@angular/core": "19.0.5", "@angular/core": "17.0.3",
"@angular/forms": "19.0.5", "@angular/forms": "17.0.3",
"@angular/material": "19.0.4", "@angular/material": "17.0.1",
"@angular/material-luxon-adapter": "19.0.4", "@angular/material-luxon-adapter": "17.0.1",
"@angular/platform-browser": "19.0.5", "@angular/platform-browser": "17.0.3",
"@angular/platform-browser-dynamic": "19.0.5", "@angular/platform-browser-dynamic": "17.0.3",
"@angular/router": "19.0.5", "@angular/router": "17.0.3",
"@jsverse/transloco": "7.5.1", "@ngneat/transloco": "6.0.0",
"apexcharts": "4.3.0", "apexcharts": "3.44.0",
"crypto-js": "4.2.0", "crypto-js": "3.3.0",
"highlight.js": "11.11.1", "highlight.js": "11.9.0",
"lodash-es": "4.17.21", "lodash-es": "4.17.21",
"luxon": "3.5.0", "luxon": "3.4.4",
"ng-apexcharts": "1.15.0", "ng-apexcharts": "1.8.0",
"ngx-quill": "27.0.0", "ngx-quill": "24.0.2",
"perfect-scrollbar": "1.5.6", "perfect-scrollbar": "1.5.5",
"quill": "2.0.3", "quill": "1.3.7",
"rxjs": "7.8.1", "rxjs": "7.8.1",
"tslib": "2.8.1", "tslib": "2.6.2",
"zone.js": "0.15.0" "zone.js": "0.14.2"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "19.0.6", "@angular-devkit/build-angular": "17.0.1",
"@angular/cli": "19.0.6", "@angular/cli": "17.0.1",
"@angular/compiler-cli": "19.0.5", "@angular/compiler-cli": "17.0.3",
"@tailwindcss/typography": "0.5.15", "@tailwindcss/typography": "0.5.10",
"@types/chroma-js": "2.4.5", "@types/chroma-js": "2.4.3",
"@types/crypto-js": "4.2.2", "@types/crypto-js": "3.1.47",
"@types/highlight.js": "10.1.0", "@types/highlight.js": "10.1.0",
"@types/jasmine": "5.1.5", "@types/jasmine": "5.1.2",
"@types/lodash": "4.17.13", "@types/lodash": "4.14.201",
"@types/lodash-es": "4.17.12", "@types/lodash-es": "4.17.11",
"@types/luxon": "3.4.2", "@types/luxon": "3.3.4",
"autoprefixer": "10.4.20", "autoprefixer": "10.4.16",
"chroma-js": "2.4.2", "chroma-js": "2.4.2",
"jasmine-core": "5.1.2", "jasmine-core": "5.1.1",
"karma": "6.4.4", "karma": "6.4.2",
"karma-chrome-launcher": "3.2.0", "karma-chrome-launcher": "3.2.0",
"karma-coverage": "2.2.1", "karma-coverage": "2.2.1",
"karma-jasmine": "5.1.0", "karma-jasmine": "5.1.0",
"karma-jasmine-html-reporter": "2.1.0", "karma-jasmine-html-reporter": "2.0.0",
"lodash": "4.17.21", "lodash": "4.17.21",
"postcss": "8.4.49", "postcss": "8.4.31",
"prettier": "3.4.2", "tailwindcss": "3.3.5",
"prettier-plugin-organize-imports": "4.1.0", "typescript": "5.2.2"
"prettier-plugin-tailwindcss": "0.6.9",
"tailwindcss": "3.4.17",
"typescript": "5.6.3"
} }
} }

View File

@ -1,11 +1,13 @@
export class FuseAnimationCurves { export class FuseAnimationCurves
{
static standard = 'cubic-bezier(0.4, 0.0, 0.2, 1)'; static standard = 'cubic-bezier(0.4, 0.0, 0.2, 1)';
static deceleration = 'cubic-bezier(0.0, 0.0, 0.2, 1)'; static deceleration = 'cubic-bezier(0.0, 0.0, 0.2, 1)';
static acceleration = 'cubic-bezier(0.4, 0.0, 1, 1)'; static acceleration = 'cubic-bezier(0.4, 0.0, 1, 1)';
static sharp = 'cubic-bezier(0.4, 0.0, 0.6, 1)'; static sharp = 'cubic-bezier(0.4, 0.0, 0.6, 1)';
} }
export class FuseAnimationDurations { export class FuseAnimationDurations
{
static complex = '375ms'; static complex = '375ms';
static entering = '225ms'; static entering = '225ms';
static exiting = '195ms'; static exiting = '195ms';

View File

@ -1,37 +1,34 @@
import { import { animate, state, style, transition, trigger } from '@angular/animations';
animate, import { FuseAnimationCurves, FuseAnimationDurations } from '@fuse/animations/defaults';
state,
style,
transition,
trigger,
} from '@angular/animations';
import {
FuseAnimationCurves,
FuseAnimationDurations,
} from '@fuse/animations/defaults';
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Expand / collapse // @ Expand / collapse
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const expandCollapse = trigger('expandCollapse', [ const expandCollapse = trigger('expandCollapse',
state( [
'void, collapsed', state('void, collapsed',
style({ style({
height: '0', height: '0',
}) }),
), ),
state('*, expanded', style('*')), state('*, expanded',
style('*'),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('void <=> false, collapsed <=> false, expanded <=> false', []), transition('void <=> false, collapsed <=> false, expanded <=> false', []),
// Transition // Transition
transition('void <=> *, collapsed <=> expanded', animate('{{timings}}'), { transition('void <=> *, collapsed <=> expanded',
params: { animate('{{timings}}'),
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`, {
}, params: {
}), timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`,
]); },
},
),
],
);
export { expandCollapse }; export { expandCollapse };

View File

@ -1,330 +1,330 @@
import { import { animate, state, style, transition, trigger } from '@angular/animations';
animate, import { FuseAnimationCurves, FuseAnimationDurations } from '@fuse/animations/defaults';
state,
style,
transition,
trigger,
} from '@angular/animations';
import {
FuseAnimationCurves,
FuseAnimationDurations,
} from '@fuse/animations/defaults';
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Fade in // @ Fade in
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const fadeIn = trigger('fadeIn', [ const fadeIn = trigger('fadeIn',
state( [
'void', state('void',
style({ style({
opacity: 0, opacity: 0,
}) }),
), ),
state( state('*',
'*', style({
style({ opacity: 1,
opacity: 1, }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('void => false', []), transition('void => false', []),
// Transition // Transition
transition('void => *', animate('{{timings}}'), { transition('void => *', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`, params: {
}, timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`,
}), },
]); },
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Fade in top // @ Fade in top
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const fadeInTop = trigger('fadeInTop', [ const fadeInTop = trigger('fadeInTop',
state( [
'void', state('void',
style({ style({
opacity: 0, opacity : 0,
transform: 'translate3d(0, -100%, 0)', transform: 'translate3d(0, -100%, 0)',
}) }),
), ),
state( state('*',
'*', style({
style({ opacity : 1,
opacity: 1, transform: 'translate3d(0, 0, 0)',
transform: 'translate3d(0, 0, 0)', }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('void => false', []), transition('void => false', []),
// Transition // Transition
transition('void => *', animate('{{timings}}'), { transition('void => *', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`, params: {
}, timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`,
}), },
]); },
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Fade in bottom // @ Fade in bottom
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const fadeInBottom = trigger('fadeInBottom', [ const fadeInBottom = trigger('fadeInBottom',
state( [
'void', state('void',
style({ style({
opacity: 0, opacity : 0,
transform: 'translate3d(0, 100%, 0)', transform: 'translate3d(0, 100%, 0)',
}) }),
), ),
state( state('*',
'*', style({
style({ opacity : 1,
opacity: 1, transform: 'translate3d(0, 0, 0)',
transform: 'translate3d(0, 0, 0)', }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('void => false', []), transition('void => false', []),
// Transition // Transition
transition('void => *', animate('{{timings}}'), { transition('void => *', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`, params: {
}, timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`,
}), },
]); },
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Fade in left // @ Fade in left
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const fadeInLeft = trigger('fadeInLeft', [ const fadeInLeft = trigger('fadeInLeft',
state( [
'void', state('void',
style({ style({
opacity: 0, opacity : 0,
transform: 'translate3d(-100%, 0, 0)', transform: 'translate3d(-100%, 0, 0)',
}) }),
), ),
state( state('*',
'*', style({
style({ opacity : 1,
opacity: 1, transform: 'translate3d(0, 0, 0)',
transform: 'translate3d(0, 0, 0)', }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('void => false', []), transition('void => false', []),
// Transition // Transition
transition('void => *', animate('{{timings}}'), { transition('void => *', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`, params: {
}, timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`,
}), },
]); },
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Fade in right // @ Fade in right
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const fadeInRight = trigger('fadeInRight', [ const fadeInRight = trigger('fadeInRight',
state( [
'void', state('void',
style({ style({
opacity: 0, opacity : 0,
transform: 'translate3d(100%, 0, 0)', transform: 'translate3d(100%, 0, 0)',
}) }),
), ),
state( state('*',
'*', style({
style({ opacity : 1,
opacity: 1, transform: 'translate3d(0, 0, 0)',
transform: 'translate3d(0, 0, 0)', }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('void => false', []), transition('void => false', []),
// Transition // Transition
transition('void => *', animate('{{timings}}'), { transition('void => *', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`, params: {
}, timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`,
}), },
]); },
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Fade out // @ Fade out
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const fadeOut = trigger('fadeOut', [ const fadeOut = trigger('fadeOut',
state( [
'*', state('*',
style({ style({
opacity: 1, opacity: 1,
}) }),
), ),
state( state('void',
'void', style({
style({ opacity: 0,
opacity: 0, }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('false => void', []), transition('false => void', []),
// Transition // Transition
transition('* => void', animate('{{timings}}'), { transition('* => void', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`, params: {
}, timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`,
}), },
]); },
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Fade out top // @ Fade out top
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const fadeOutTop = trigger('fadeOutTop', [ const fadeOutTop = trigger('fadeOutTop',
state( [
'*', state('*',
style({ style({
opacity: 1, opacity : 1,
transform: 'translate3d(0, 0, 0)', transform: 'translate3d(0, 0, 0)',
}) }),
), ),
state( state('void',
'void', style({
style({ opacity : 0,
opacity: 0, transform: 'translate3d(0, -100%, 0)',
transform: 'translate3d(0, -100%, 0)', }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('false => void', []), transition('false => void', []),
// Transition // Transition
transition('* => void', animate('{{timings}}'), { transition('* => void', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`, params: {
}, timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`,
}), },
]); },
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Fade out bottom // @ Fade out bottom
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const fadeOutBottom = trigger('fadeOutBottom', [ const fadeOutBottom = trigger('fadeOutBottom',
state( [
'*', state('*',
style({ style({
opacity: 1, opacity : 1,
transform: 'translate3d(0, 0, 0)', transform: 'translate3d(0, 0, 0)',
}) }),
), ),
state( state('void',
'void', style({
style({ opacity : 0,
opacity: 0, transform: 'translate3d(0, 100%, 0)',
transform: 'translate3d(0, 100%, 0)', }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('false => void', []), transition('false => void', []),
// Transition // Transition
transition('* => void', animate('{{timings}}'), { transition('* => void', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`, params: {
}, timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`,
}), },
]); },
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Fade out left // @ Fade out left
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const fadeOutLeft = trigger('fadeOutLeft', [ const fadeOutLeft = trigger('fadeOutLeft',
state( [
'*', state('*',
style({ style({
opacity: 1, opacity : 1,
transform: 'translate3d(0, 0, 0)', transform: 'translate3d(0, 0, 0)',
}) }),
), ),
state( state('void',
'void', style({
style({ opacity : 0,
opacity: 0, transform: 'translate3d(-100%, 0, 0)',
transform: 'translate3d(-100%, 0, 0)', }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('false => void', []), transition('false => void', []),
// Transition // Transition
transition('* => void', animate('{{timings}}'), { transition('* => void', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`, params: {
}, timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`,
}), },
]); },
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Fade out right // @ Fade out right
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const fadeOutRight = trigger('fadeOutRight', [ const fadeOutRight = trigger('fadeOutRight',
state( [
'*', state('*',
style({ style({
opacity: 1, opacity : 1,
transform: 'translate3d(0, 0, 0)', transform: 'translate3d(0, 0, 0)',
}) }),
), ),
state( state('void',
'void', style({
style({ opacity : 0,
opacity: 0, transform: 'translate3d(100%, 0, 0)',
transform: 'translate3d(100%, 0, 0)', }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('false => void', []), transition('false => void', []),
// Transition // Transition
transition('* => void', animate('{{timings}}'), { transition('* => void', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`, params: {
}, timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`,
}), },
]); },
),
],
);
export { export { fadeIn, fadeInTop, fadeInBottom, fadeInLeft, fadeInRight, fadeOut, fadeOutTop, fadeOutBottom, fadeOutLeft, fadeOutRight };
fadeIn,
fadeInBottom,
fadeInLeft,
fadeInRight,
fadeInTop,
fadeOut,
fadeOutBottom,
fadeOutLeft,
fadeOutRight,
fadeOutTop,
};

View File

@ -1,50 +1,15 @@
import { expandCollapse } from '@fuse/animations/expand-collapse'; import { expandCollapse } from '@fuse/animations/expand-collapse';
import { import { fadeIn, fadeInBottom, fadeInLeft, fadeInRight, fadeInTop, fadeOut, fadeOutBottom, fadeOutLeft, fadeOutRight, fadeOutTop } from '@fuse/animations/fade';
fadeIn,
fadeInBottom,
fadeInLeft,
fadeInRight,
fadeInTop,
fadeOut,
fadeOutBottom,
fadeOutLeft,
fadeOutRight,
fadeOutTop,
} from '@fuse/animations/fade';
import { shake } from '@fuse/animations/shake'; import { shake } from '@fuse/animations/shake';
import { import { slideInBottom, slideInLeft, slideInRight, slideInTop, slideOutBottom, slideOutLeft, slideOutRight, slideOutTop } from '@fuse/animations/slide';
slideInBottom,
slideInLeft,
slideInRight,
slideInTop,
slideOutBottom,
slideOutLeft,
slideOutRight,
slideOutTop,
} from '@fuse/animations/slide';
import { zoomIn, zoomOut } from '@fuse/animations/zoom'; import { zoomIn, zoomOut } from '@fuse/animations/zoom';
export const fuseAnimations = [ export const fuseAnimations = [
expandCollapse, expandCollapse,
fadeIn, fadeIn, fadeInTop, fadeInBottom, fadeInLeft, fadeInRight,
fadeInTop, fadeOut, fadeOutTop, fadeOutBottom, fadeOutLeft, fadeOutRight,
fadeInBottom,
fadeInLeft,
fadeInRight,
fadeOut,
fadeOutTop,
fadeOutBottom,
fadeOutLeft,
fadeOutRight,
shake, shake,
slideInTop, slideInTop, slideInBottom, slideInLeft, slideInRight,
slideInBottom, slideOutTop, slideOutBottom, slideOutLeft, slideOutRight,
slideInLeft, zoomIn, zoomOut,
slideInRight,
slideOutTop,
slideOutBottom,
slideOutLeft,
slideOutRight,
zoomIn,
zoomOut,
]; ];

View File

@ -1,78 +1,73 @@
import { import { animate, keyframes, style, transition, trigger } from '@angular/animations';
animate,
keyframes,
style,
transition,
trigger,
} from '@angular/animations';
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Shake // @ Shake
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const shake = trigger('shake', [ const shake = trigger('shake',
// Prevent the transition if the state is false [
transition('void => false', []),
// Transition // Prevent the transition if the state is false
transition( transition('void => false', []),
'void => *, * => true',
[ // Transition
animate( transition('void => *, * => true',
'{{timings}}', [
keyframes([ animate('{{timings}}',
style({ keyframes([
transform: 'translate3d(0, 0, 0)', style({
offset: 0, transform: 'translate3d(0, 0, 0)',
}), offset : 0,
style({ }),
transform: 'translate3d(-10px, 0, 0)', style({
offset: 0.1, transform: 'translate3d(-10px, 0, 0)',
}), offset : 0.1,
style({ }),
transform: 'translate3d(10px, 0, 0)', style({
offset: 0.2, transform: 'translate3d(10px, 0, 0)',
}), offset : 0.2,
style({ }),
transform: 'translate3d(-10px, 0, 0)', style({
offset: 0.3, transform: 'translate3d(-10px, 0, 0)',
}), offset : 0.3,
style({ }),
transform: 'translate3d(10px, 0, 0)', style({
offset: 0.4, transform: 'translate3d(10px, 0, 0)',
}), offset : 0.4,
style({ }),
transform: 'translate3d(-10px, 0, 0)', style({
offset: 0.5, transform: 'translate3d(-10px, 0, 0)',
}), offset : 0.5,
style({ }),
transform: 'translate3d(10px, 0, 0)', style({
offset: 0.6, transform: 'translate3d(10px, 0, 0)',
}), offset : 0.6,
style({ }),
transform: 'translate3d(-10px, 0, 0)', style({
offset: 0.7, transform: 'translate3d(-10px, 0, 0)',
}), offset : 0.7,
style({ }),
transform: 'translate3d(10px, 0, 0)', style({
offset: 0.8, transform: 'translate3d(10px, 0, 0)',
}), offset : 0.8,
style({ }),
transform: 'translate3d(-10px, 0, 0)', style({
offset: 0.9, transform: 'translate3d(-10px, 0, 0)',
}), offset : 0.9,
style({ }),
transform: 'translate3d(0, 0, 0)', style({
offset: 1, transform: 'translate3d(0, 0, 0)',
}), offset : 1,
]) }),
), ]),
], ),
{ ],
params: { {
timings: '0.8s cubic-bezier(0.455, 0.03, 0.515, 0.955)', params: {
timings: '0.8s cubic-bezier(0.455, 0.03, 0.515, 0.955)',
},
}, },
} ),
), ],
]); );
export { shake }; export { shake };

View File

@ -1,254 +1,252 @@
import { import { animate, state, style, transition, trigger } from '@angular/animations';
animate, import { FuseAnimationCurves, FuseAnimationDurations } from '@fuse/animations/defaults';
state,
style,
transition,
trigger,
} from '@angular/animations';
import {
FuseAnimationCurves,
FuseAnimationDurations,
} from '@fuse/animations/defaults';
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Slide in top // @ Slide in top
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const slideInTop = trigger('slideInTop', [ const slideInTop = trigger('slideInTop',
state( [
'void', state('void',
style({ style({
transform: 'translate3d(0, -100%, 0)', transform: 'translate3d(0, -100%, 0)',
}) }),
), ),
state( state('*',
'*', style({
style({ transform: 'translate3d(0, 0, 0)',
transform: 'translate3d(0, 0, 0)', }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('void => false', []), transition('void => false', []),
// Transition // Transition
transition('void => *', animate('{{timings}}'), { transition('void => *', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`, params: {
}, timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`,
}), },
]); },
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Slide in bottom // @ Slide in bottom
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const slideInBottom = trigger('slideInBottom', [ const slideInBottom = trigger('slideInBottom',
state( [
'void', state('void',
style({ style({
transform: 'translate3d(0, 100%, 0)', transform: 'translate3d(0, 100%, 0)',
}) }),
), ),
state( state('*',
'*', style({
style({ transform: 'translate3d(0, 0, 0)',
transform: 'translate3d(0, 0, 0)', }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('void => false', []), transition('void => false', []),
// Transition // Transition
transition('void => *', animate('{{timings}}'), { transition('void => *', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`, params: {
}, timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`,
}), },
]); },
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Slide in left // @ Slide in left
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const slideInLeft = trigger('slideInLeft', [ const slideInLeft = trigger('slideInLeft',
state( [
'void', state('void',
style({ style({
transform: 'translate3d(-100%, 0, 0)', transform: 'translate3d(-100%, 0, 0)',
}) }),
), ),
state( state('*',
'*', style({
style({ transform: 'translate3d(0, 0, 0)',
transform: 'translate3d(0, 0, 0)', }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('void => false', []), transition('void => false', []),
// Transition // Transition
transition('void => *', animate('{{timings}}'), { transition('void => *', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`, params: {
}, timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`,
}), },
]); },
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Slide in right // @ Slide in right
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const slideInRight = trigger('slideInRight', [ const slideInRight = trigger('slideInRight',
state( [
'void', state('void',
style({ style({
transform: 'translate3d(100%, 0, 0)', transform: 'translate3d(100%, 0, 0)',
}) }),
), ),
state( state('*',
'*', style({
style({ transform: 'translate3d(0, 0, 0)',
transform: 'translate3d(0, 0, 0)', }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('void => false', []), transition('void => false', []),
// Transition // Transition
transition('void => *', animate('{{timings}}'), { transition('void => *', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`, params: {
}, timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`,
}), },
]); },
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Slide out top // @ Slide out top
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const slideOutTop = trigger('slideOutTop', [ const slideOutTop = trigger('slideOutTop',
state( [
'*', state('*',
style({ style({
transform: 'translate3d(0, 0, 0)', transform: 'translate3d(0, 0, 0)',
}) }),
), ),
state( state('void',
'void', style({
style({ transform: 'translate3d(0, -100%, 0)',
transform: 'translate3d(0, -100%, 0)', }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('false => void', []), transition('false => void', []),
// Transition // Transition
transition('* => void', animate('{{timings}}'), { transition('* => void', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`, params: {
}, timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`,
}), },
]); },
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Slide out bottom // @ Slide out bottom
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const slideOutBottom = trigger('slideOutBottom', [ const slideOutBottom = trigger('slideOutBottom',
state( [
'*', state('*',
style({ style({
transform: 'translate3d(0, 0, 0)', transform: 'translate3d(0, 0, 0)',
}) }),
), ),
state( state('void',
'void', style({
style({ transform: 'translate3d(0, 100%, 0)',
transform: 'translate3d(0, 100%, 0)', }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('false => void', []), transition('false => void', []),
// Transition // Transition
transition('* => void', animate('{{timings}}'), { transition('* => void', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`, params: {
}, timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`,
}), },
]); },
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Slide out left // @ Slide out left
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const slideOutLeft = trigger('slideOutLeft', [ const slideOutLeft = trigger('slideOutLeft',
state( [
'*', state('*',
style({ style({
transform: 'translate3d(0, 0, 0)', transform: 'translate3d(0, 0, 0)',
}) }),
), ),
state( state('void',
'void', style({
style({ transform: 'translate3d(-100%, 0, 0)',
transform: 'translate3d(-100%, 0, 0)', }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('false => void', []), transition('false => void', []),
// Transition // Transition
transition('* => void', animate('{{timings}}'), { transition('* => void', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`, params: {
}, timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`,
}), },
]); },
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Slide out right // @ Slide out right
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const slideOutRight = trigger('slideOutRight', [ const slideOutRight = trigger('slideOutRight',
state( [
'*', state('*',
style({ style({
transform: 'translate3d(0, 0, 0)', transform: 'translate3d(0, 0, 0)',
}) }),
), ),
state( state('void',
'void', style({
style({ transform: 'translate3d(100%, 0, 0)',
transform: 'translate3d(100%, 0, 0)', }),
}) ),
),
// Prevent the transition if the state is false // Prevent the transition if the state is false
transition('false => void', []), transition('false => void', []),
// Transition // Transition
transition('* => void', animate('{{timings}}'), { transition('* => void', animate('{{timings}}'),
params: { {
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`, params: {
}, timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`,
}), },
]); },
),
],
);
export { export { slideInTop, slideInBottom, slideInLeft, slideInRight, slideOutTop, slideOutBottom, slideOutLeft, slideOutRight };
slideInBottom,
slideInLeft,
slideInRight,
slideInTop,
slideOutBottom,
slideOutLeft,
slideOutRight,
slideOutTop,
};

View File

@ -1,75 +1,73 @@
import { import { animate, state, style, transition, trigger } from '@angular/animations';
animate, import { FuseAnimationCurves, FuseAnimationDurations } from '@fuse/animations/defaults';
state,
style,
transition,
trigger,
} from '@angular/animations';
import {
FuseAnimationCurves,
FuseAnimationDurations,
} from '@fuse/animations/defaults';
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Zoom in // @ Zoom in
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const zoomIn = trigger('zoomIn', [ const zoomIn = trigger('zoomIn',
state( [
'void',
style({
opacity: 0,
transform: 'scale(0.5)',
})
),
state( state('void',
'*', style({
style({ opacity : 0,
opacity: 1, transform: 'scale(0.5)',
transform: 'scale(1)', }),
}) ),
),
// Prevent the transition if the state is false state('*',
transition('void => false', []), style({
opacity : 1,
transform: 'scale(1)',
}),
),
// Transition // Prevent the transition if the state is false
transition('void => *', animate('{{timings}}'), { transition('void => false', []),
params: {
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`, // Transition
}, transition('void => *', animate('{{timings}}'),
}), {
]); params: {
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`,
},
},
),
],
);
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Zoom out // @ Zoom out
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
const zoomOut = trigger('zoomOut', [ const zoomOut = trigger('zoomOut',
state( [
'*',
style({
opacity: 1,
transform: 'scale(1)',
})
),
state( state('*',
'void', style({
style({ opacity : 1,
opacity: 0, transform: 'scale(1)',
transform: 'scale(0.5)', }),
}) ),
),
// Prevent the transition if the state is false state('void',
transition('false => void', []), style({
opacity : 0,
transform: 'scale(0.5)',
}),
),
// Transition // Prevent the transition if the state is false
transition('* => void', animate('{{timings}}'), { transition('false => void', []),
params: {
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`, // Transition
}, transition('* => void', animate('{{timings}}'),
}), {
]); params: {
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`,
},
},
),
],
);
export { zoomIn, zoomOut }; export { zoomIn, zoomOut };

View File

@ -1,93 +1,82 @@
@if (!dismissible || (dismissible && !dismissed)) { <div
class="fuse-alert-container"
*ngIf="!dismissible || dismissible && !dismissed"
[@fadeIn]="!dismissed"
[@fadeOut]="!dismissed">
<!-- Border -->
<div <div
class="fuse-alert-container" class="fuse-alert-border"
[@fadeIn]="!dismissed" *ngIf="appearance === 'border'"></div>
[@fadeOut]="!dismissed"
>
<!-- Border -->
@if (appearance === 'border') {
<div class="fuse-alert-border"></div>
}
<!-- Icon --> <!-- Icon -->
@if (showIcon) { <div
<div class="fuse-alert-icon"> class="fuse-alert-icon"
<!-- Custom icon --> *ngIf="showIcon">
<div class="fuse-alert-custom-icon">
<ng-content select="[fuseAlertIcon]"></ng-content> <!-- Custom icon -->
</div> <div class="fuse-alert-custom-icon">
<ng-content select="[fuseAlertIcon]"></ng-content>
</div>
<!-- Default icons -->
<div class="fuse-alert-default-icon">
<mat-icon
*ngIf="type === 'primary'"
[svgIcon]="'heroicons_solid:check-circle'"></mat-icon>
<mat-icon
*ngIf="type === 'accent'"
[svgIcon]="'heroicons_solid:check-circle'"></mat-icon>
<mat-icon
*ngIf="type === 'warn'"
[svgIcon]="'heroicons_solid:x-circle'"></mat-icon>
<mat-icon
*ngIf="type === 'basic'"
[svgIcon]="'heroicons_solid:check-circle'"></mat-icon>
<mat-icon
*ngIf="type === 'info'"
[svgIcon]="'heroicons_solid:information-circle'"></mat-icon>
<mat-icon
*ngIf="type === 'success'"
[svgIcon]="'heroicons_solid:check-circle'"></mat-icon>
<mat-icon
*ngIf="type === 'warning'"
[svgIcon]="'heroicons_solid:exclamation-triangle'"></mat-icon>
<mat-icon
*ngIf="type === 'error'"
[svgIcon]="'heroicons_solid:x-circle'"></mat-icon>
<!-- Default icons -->
<div class="fuse-alert-default-icon">
@if (type === 'primary') {
<mat-icon
[svgIcon]="'heroicons_solid:check-circle'"
></mat-icon>
}
@if (type === 'accent') {
<mat-icon
[svgIcon]="'heroicons_solid:check-circle'"
></mat-icon>
}
@if (type === 'warn') {
<mat-icon
[svgIcon]="'heroicons_solid:x-circle'"
></mat-icon>
}
@if (type === 'basic') {
<mat-icon
[svgIcon]="'heroicons_solid:check-circle'"
></mat-icon>
}
@if (type === 'info') {
<mat-icon
[svgIcon]="'heroicons_solid:information-circle'"
></mat-icon>
}
@if (type === 'success') {
<mat-icon
[svgIcon]="'heroicons_solid:check-circle'"
></mat-icon>
}
@if (type === 'warning') {
<mat-icon
[svgIcon]="'heroicons_solid:exclamation-triangle'"
></mat-icon>
}
@if (type === 'error') {
<mat-icon
[svgIcon]="'heroicons_solid:x-circle'"
></mat-icon>
}
</div>
</div>
}
<!-- Content -->
<div class="fuse-alert-content">
<div class="fuse-alert-title">
<ng-content select="[fuseAlertTitle]"></ng-content>
</div>
<div class="fuse-alert-message">
<ng-content></ng-content>
</div>
</div> </div>
<!-- Dismiss button -->
<button
class="fuse-alert-dismiss-button"
mat-icon-button
(click)="dismiss()"
>
<mat-icon [svgIcon]="'heroicons_solid:x-mark'"></mat-icon>
</button>
</div> </div>
}
<!-- Content -->
<div class="fuse-alert-content">
<div class="fuse-alert-title">
<ng-content select="[fuseAlertTitle]"></ng-content>
</div>
<div class="fuse-alert-message">
<ng-content></ng-content>
</div>
</div>
<!-- Dismiss button -->
<button
class="fuse-alert-dismiss-button"
mat-icon-button
(click)="dismiss()">
<mat-icon [svgIcon]="'heroicons_solid:x-mark'"></mat-icon>
</button>
</div>

View File

@ -33,6 +33,7 @@ fuse-alert {
} }
.fuse-alert-default-icon { .fuse-alert-default-icon {
.mat-icon { .mat-icon {
@apply icon-size-5; @apply icon-size-5;
} }
@ -69,6 +70,7 @@ fuse-alert {
/* Alert that comes after the title */ /* Alert that comes after the title */
+ .fuse-alert-message { + .fuse-alert-message {
&:not(:empty) { &:not(:empty) {
margin-top: 4px; margin-top: 4px;
} }
@ -106,7 +108,9 @@ fuse-alert {
/* Dismissible */ /* Dismissible */
&.fuse-alert-dismissible { &.fuse-alert-dismissible {
.fuse-alert-container { .fuse-alert-container {
.fuse-alert-content { .fuse-alert-content {
margin-right: 32px; margin-right: 32px;
} }
@ -114,7 +118,9 @@ fuse-alert {
} }
&:not(.fuse-alert-dismissible) { &:not(.fuse-alert-dismissible) {
.fuse-alert-container { .fuse-alert-container {
.fuse-alert-dismiss-button { .fuse-alert-dismiss-button {
display: none !important; display: none !important;
} }
@ -123,11 +129,12 @@ fuse-alert {
/* Border */ /* Border */
&.fuse-alert-appearance-border { &.fuse-alert-appearance-border {
.fuse-alert-container { .fuse-alert-container {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
border-radius: 6px; border-radius: 6px;
@apply bg-card shadow-md; @apply shadow-md bg-card;
.fuse-alert-border { .fuse-alert-border {
position: absolute; position: absolute;
@ -144,7 +151,9 @@ fuse-alert {
/* Primary */ /* Primary */
&.fuse-alert-type-primary { &.fuse-alert-type-primary {
.fuse-alert-container { .fuse-alert-container {
.fuse-alert-border { .fuse-alert-border {
@apply bg-primary; @apply bg-primary;
} }
@ -179,7 +188,9 @@ fuse-alert {
/* Accent */ /* Accent */
&.fuse-alert-type-accent { &.fuse-alert-type-accent {
.fuse-alert-container { .fuse-alert-container {
.fuse-alert-border { .fuse-alert-border {
@apply bg-accent; @apply bg-accent;
} }
@ -214,7 +225,9 @@ fuse-alert {
/* Warn */ /* Warn */
&.fuse-alert-type-warn { &.fuse-alert-type-warn {
.fuse-alert-container { .fuse-alert-container {
.fuse-alert-border { .fuse-alert-border {
@apply bg-warn; @apply bg-warn;
} }
@ -249,7 +262,9 @@ fuse-alert {
/* Basic */ /* Basic */
&.fuse-alert-type-basic { &.fuse-alert-type-basic {
.fuse-alert-container { .fuse-alert-container {
.fuse-alert-border { .fuse-alert-border {
@apply bg-gray-600; @apply bg-gray-600;
} }
@ -284,7 +299,9 @@ fuse-alert {
/* Info */ /* Info */
&.fuse-alert-type-info { &.fuse-alert-type-info {
.fuse-alert-container { .fuse-alert-container {
.fuse-alert-border { .fuse-alert-border {
@apply bg-blue-600; @apply bg-blue-600;
} }
@ -319,7 +336,9 @@ fuse-alert {
/* Success */ /* Success */
&.fuse-alert-type-success { &.fuse-alert-type-success {
.fuse-alert-container { .fuse-alert-container {
.fuse-alert-border { .fuse-alert-border {
@apply bg-green-500; @apply bg-green-500;
} }
@ -354,7 +373,9 @@ fuse-alert {
/* Warning */ /* Warning */
&.fuse-alert-type-warning { &.fuse-alert-type-warning {
.fuse-alert-container { .fuse-alert-container {
.fuse-alert-border { .fuse-alert-border {
@apply bg-amber-500; @apply bg-amber-500;
} }
@ -389,7 +410,9 @@ fuse-alert {
/* Error */ /* Error */
&.fuse-alert-type-error { &.fuse-alert-type-error {
.fuse-alert-container { .fuse-alert-container {
.fuse-alert-border { .fuse-alert-border {
@apply bg-red-600; @apply bg-red-600;
} }
@ -425,6 +448,7 @@ fuse-alert {
/* Fill */ /* Fill */
&.fuse-alert-appearance-fill { &.fuse-alert-appearance-fill {
.fuse-alert-container { .fuse-alert-container {
border-radius: 6px; border-radius: 6px;
@ -435,6 +459,7 @@ fuse-alert {
/* Primary */ /* Primary */
&.fuse-alert-type-primary { &.fuse-alert-type-primary {
.fuse-alert-container { .fuse-alert-container {
@apply bg-primary-600; @apply bg-primary-600;
@ -451,13 +476,14 @@ fuse-alert {
} }
code { code {
@apply bg-primary-200 text-primary-800; @apply text-primary-800 bg-primary-200;
} }
} }
} }
/* Accent */ /* Accent */
&.fuse-alert-type-accent { &.fuse-alert-type-accent {
.fuse-alert-container { .fuse-alert-container {
@apply bg-accent-600; @apply bg-accent-600;
@ -474,13 +500,14 @@ fuse-alert {
} }
code { code {
@apply bg-accent-200 text-accent-800; @apply text-accent-800 bg-accent-200;
} }
} }
} }
/* Warn */ /* Warn */
&.fuse-alert-type-warn { &.fuse-alert-type-warn {
.fuse-alert-container { .fuse-alert-container {
@apply bg-warn-600; @apply bg-warn-600;
@ -497,13 +524,14 @@ fuse-alert {
} }
code { code {
@apply bg-warn-200 text-warn-800; @apply text-warn-800 bg-warn-200;
} }
} }
} }
/* Basic */ /* Basic */
&.fuse-alert-type-basic { &.fuse-alert-type-basic {
.fuse-alert-container { .fuse-alert-container {
@apply bg-gray-600; @apply bg-gray-600;
@ -527,6 +555,7 @@ fuse-alert {
/* Info */ /* Info */
&.fuse-alert-type-info { &.fuse-alert-type-info {
.fuse-alert-container { .fuse-alert-container {
@apply bg-blue-600; @apply bg-blue-600;
@ -550,6 +579,7 @@ fuse-alert {
/* Success */ /* Success */
&.fuse-alert-type-success { &.fuse-alert-type-success {
.fuse-alert-container { .fuse-alert-container {
@apply bg-green-600; @apply bg-green-600;
@ -573,6 +603,7 @@ fuse-alert {
/* Warning */ /* Warning */
&.fuse-alert-type-warning { &.fuse-alert-type-warning {
.fuse-alert-container { .fuse-alert-container {
@apply bg-amber-500; @apply bg-amber-500;
@ -596,6 +627,7 @@ fuse-alert {
/* Error */ /* Error */
&.fuse-alert-type-error { &.fuse-alert-type-error {
.fuse-alert-container { .fuse-alert-container {
@apply bg-red-600; @apply bg-red-600;
@ -620,14 +652,16 @@ fuse-alert {
/* Outline */ /* Outline */
&.fuse-alert-appearance-outline { &.fuse-alert-appearance-outline {
.fuse-alert-container { .fuse-alert-container {
border-radius: 6px; border-radius: 6px;
} }
/* Primary */ /* Primary */
&.fuse-alert-type-primary { &.fuse-alert-type-primary {
.fuse-alert-container { .fuse-alert-container {
@apply bg-primary-50 ring-1 ring-inset ring-primary-400; @apply bg-primary-50 ring-1 ring-primary-400 ring-inset;
.fuse-alert-icon { .fuse-alert-icon {
@apply text-primary-600; @apply text-primary-600;
@ -643,7 +677,7 @@ fuse-alert {
} }
code { code {
@apply bg-primary-200 text-primary-800; @apply text-primary-800 bg-primary-200;
} }
.dark & { .dark & {
@ -667,8 +701,9 @@ fuse-alert {
/* Accent */ /* Accent */
&.fuse-alert-type-accent { &.fuse-alert-type-accent {
.fuse-alert-container { .fuse-alert-container {
@apply bg-accent-100 ring-1 ring-inset ring-accent-400; @apply bg-accent-100 ring-1 ring-accent-400 ring-inset;
.fuse-alert-icon { .fuse-alert-icon {
@apply text-accent-600; @apply text-accent-600;
@ -684,7 +719,7 @@ fuse-alert {
} }
code { code {
@apply bg-accent-200 text-accent-800; @apply text-accent-800 bg-accent-200;
} }
.dark & { .dark & {
@ -708,8 +743,9 @@ fuse-alert {
/* Warn */ /* Warn */
&.fuse-alert-type-warn { &.fuse-alert-type-warn {
.fuse-alert-container { .fuse-alert-container {
@apply bg-warn-50 ring-1 ring-inset ring-warn-400; @apply bg-warn-50 ring-1 ring-warn-400 ring-inset;
.fuse-alert-icon { .fuse-alert-icon {
@apply text-warn-600; @apply text-warn-600;
@ -725,7 +761,7 @@ fuse-alert {
} }
code { code {
@apply bg-warn-200 text-warn-800; @apply text-warn-800 bg-warn-200;
} }
.dark & { .dark & {
@ -749,8 +785,9 @@ fuse-alert {
/* Basic */ /* Basic */
&.fuse-alert-type-basic { &.fuse-alert-type-basic {
.fuse-alert-container { .fuse-alert-container {
@apply bg-gray-100 ring-1 ring-inset ring-gray-400; @apply bg-gray-100 ring-1 ring-gray-400 ring-inset;
.fuse-alert-icon { .fuse-alert-icon {
@apply text-gray-600; @apply text-gray-600;
@ -790,8 +827,9 @@ fuse-alert {
/* Info */ /* Info */
&.fuse-alert-type-info { &.fuse-alert-type-info {
.fuse-alert-container { .fuse-alert-container {
@apply bg-blue-50 ring-1 ring-inset ring-blue-400; @apply bg-blue-50 ring-1 ring-blue-400 ring-inset;
.fuse-alert-icon { .fuse-alert-icon {
@apply text-blue-600; @apply text-blue-600;
@ -831,8 +869,9 @@ fuse-alert {
/* Success */ /* Success */
&.fuse-alert-type-success { &.fuse-alert-type-success {
.fuse-alert-container { .fuse-alert-container {
@apply bg-green-50 ring-1 ring-inset ring-green-400; @apply bg-green-50 ring-1 ring-green-400 ring-inset;
.fuse-alert-icon { .fuse-alert-icon {
@apply text-green-600; @apply text-green-600;
@ -872,8 +911,9 @@ fuse-alert {
/* Warning */ /* Warning */
&.fuse-alert-type-warning { &.fuse-alert-type-warning {
.fuse-alert-container { .fuse-alert-container {
@apply bg-amber-50 ring-1 ring-inset ring-amber-400; @apply bg-amber-50 ring-1 ring-amber-400 ring-inset;
.fuse-alert-icon { .fuse-alert-icon {
@apply text-amber-600; @apply text-amber-600;
@ -913,8 +953,9 @@ fuse-alert {
/* Error */ /* Error */
&.fuse-alert-type-error { &.fuse-alert-type-error {
.fuse-alert-container { .fuse-alert-container {
@apply bg-red-50 ring-1 ring-inset ring-red-400; @apply bg-red-50 ring-1 ring-red-400 ring-inset;
.fuse-alert-icon { .fuse-alert-icon {
@apply text-red-600; @apply text-red-600;
@ -955,12 +996,14 @@ fuse-alert {
/* Soft */ /* Soft */
&.fuse-alert-appearance-soft { &.fuse-alert-appearance-soft {
.fuse-alert-container { .fuse-alert-container {
border-radius: 6px; border-radius: 6px;
} }
/* Primary */ /* Primary */
&.fuse-alert-type-primary { &.fuse-alert-type-primary {
.fuse-alert-container { .fuse-alert-container {
@apply bg-primary-50; @apply bg-primary-50;
@ -978,7 +1021,7 @@ fuse-alert {
} }
code { code {
@apply bg-primary-200 text-primary-800; @apply text-primary-800 bg-primary-200;
} }
.dark & { .dark & {
@ -1002,6 +1045,7 @@ fuse-alert {
/* Accent */ /* Accent */
&.fuse-alert-type-accent { &.fuse-alert-type-accent {
.fuse-alert-container { .fuse-alert-container {
@apply bg-accent-100; @apply bg-accent-100;
@ -1019,7 +1063,7 @@ fuse-alert {
} }
code { code {
@apply bg-accent-200 text-accent-800; @apply text-accent-800 bg-accent-200;
} }
.dark & { .dark & {
@ -1043,6 +1087,7 @@ fuse-alert {
/* Warn */ /* Warn */
&.fuse-alert-type-warn { &.fuse-alert-type-warn {
.fuse-alert-container { .fuse-alert-container {
@apply bg-warn-50; @apply bg-warn-50;
@ -1060,7 +1105,7 @@ fuse-alert {
} }
code { code {
@apply bg-warn-200 text-warn-800; @apply text-warn-800 bg-warn-200;
} }
.dark & { .dark & {
@ -1084,6 +1129,7 @@ fuse-alert {
/* Basic */ /* Basic */
&.fuse-alert-type-basic { &.fuse-alert-type-basic {
.fuse-alert-container { .fuse-alert-container {
@apply bg-gray-100; @apply bg-gray-100;
@ -1125,6 +1171,7 @@ fuse-alert {
/* Info */ /* Info */
&.fuse-alert-type-info { &.fuse-alert-type-info {
.fuse-alert-container { .fuse-alert-container {
@apply bg-blue-50; @apply bg-blue-50;
@ -1166,6 +1213,7 @@ fuse-alert {
/* Success */ /* Success */
&.fuse-alert-type-success { &.fuse-alert-type-success {
.fuse-alert-container { .fuse-alert-container {
@apply bg-green-50; @apply bg-green-50;
@ -1207,6 +1255,7 @@ fuse-alert {
/* Warning */ /* Warning */
&.fuse-alert-type-warning { &.fuse-alert-type-warning {
.fuse-alert-container { .fuse-alert-container {
@apply bg-amber-50; @apply bg-amber-50;
@ -1248,6 +1297,7 @@ fuse-alert {
/* Error */ /* Error */
&.fuse-alert-type-error { &.fuse-alert-type-error {
.fuse-alert-container { .fuse-alert-container {
@apply bg-red-50; @apply bg-red-50;

View File

@ -1,63 +1,54 @@
import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';
import { NgIf } from '@angular/common';
import { import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostBinding, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
HostBinding,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
SimpleChanges,
ViewEncapsulation,
inject,
} from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { fuseAnimations } from '@fuse/animations'; import { fuseAnimations } from '@fuse/animations';
import { FuseAlertService } from '@fuse/components/alert/alert.service'; import { FuseAlertService } from '@fuse/components/alert/alert.service';
import { import { FuseAlertAppearance, FuseAlertType } from '@fuse/components/alert/alert.types';
FuseAlertAppearance,
FuseAlertType,
} from '@fuse/components/alert/alert.types';
import { FuseUtilsService } from '@fuse/services/utils/utils.service'; import { FuseUtilsService } from '@fuse/services/utils/utils.service';
import { Subject, filter, takeUntil } from 'rxjs'; import { filter, Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'fuse-alert', selector : 'fuse-alert',
templateUrl: './alert.component.html', templateUrl : './alert.component.html',
styleUrls: ['./alert.component.scss'], styleUrls : ['./alert.component.scss'],
encapsulation: ViewEncapsulation.None, encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
animations: fuseAnimations, animations : fuseAnimations,
exportAs: 'fuseAlert', exportAs : 'fuseAlert',
imports: [MatIconModule, MatButtonModule], standalone : true,
imports : [NgIf, MatIconModule, MatButtonModule],
}) })
export class FuseAlertComponent implements OnChanges, OnInit, OnDestroy { export class FuseAlertComponent implements OnChanges, OnInit, OnDestroy
{
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
static ngAcceptInputType_dismissible: BooleanInput; static ngAcceptInputType_dismissible: BooleanInput;
static ngAcceptInputType_dismissed: BooleanInput; static ngAcceptInputType_dismissed: BooleanInput;
static ngAcceptInputType_showIcon: BooleanInput; static ngAcceptInputType_showIcon: BooleanInput;
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
private _changeDetectorRef = inject(ChangeDetectorRef);
private _fuseAlertService = inject(FuseAlertService);
private _fuseUtilsService = inject(FuseUtilsService);
@Input() appearance: FuseAlertAppearance = 'soft'; @Input() appearance: FuseAlertAppearance = 'soft';
@Input() dismissed: boolean = false; @Input() dismissed: boolean = false;
@Input() dismissible: boolean = false; @Input() dismissible: boolean = false;
@Input() name: string = this._fuseUtilsService.randomId(); @Input() name: string = this._fuseUtilsService.randomId();
@Input() showIcon: boolean = true; @Input() showIcon: boolean = true;
@Input() type: FuseAlertType = 'primary'; @Input() type: FuseAlertType = 'primary';
@Output() readonly dismissedChanged: EventEmitter<boolean> = @Output() readonly dismissedChanged: EventEmitter<boolean> = new EventEmitter<boolean>();
new EventEmitter<boolean>();
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _fuseAlertService: FuseAlertService,
private _fuseUtilsService: FuseUtilsService,
)
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Accessors // @ Accessors
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -65,24 +56,25 @@ export class FuseAlertComponent implements OnChanges, OnInit, OnDestroy {
/** /**
* Host binding for component classes * Host binding for component classes
*/ */
@HostBinding('class') get classList(): any { @HostBinding('class') get classList(): any
{
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
return { return {
'fuse-alert-appearance-border': this.appearance === 'border', 'fuse-alert-appearance-border' : this.appearance === 'border',
'fuse-alert-appearance-fill': this.appearance === 'fill', 'fuse-alert-appearance-fill' : this.appearance === 'fill',
'fuse-alert-appearance-outline': this.appearance === 'outline', 'fuse-alert-appearance-outline': this.appearance === 'outline',
'fuse-alert-appearance-soft': this.appearance === 'soft', 'fuse-alert-appearance-soft' : this.appearance === 'soft',
'fuse-alert-dismissed': this.dismissed, 'fuse-alert-dismissed' : this.dismissed,
'fuse-alert-dismissible': this.dismissible, 'fuse-alert-dismissible' : this.dismissible,
'fuse-alert-show-icon': this.showIcon, 'fuse-alert-show-icon' : this.showIcon,
'fuse-alert-type-primary': this.type === 'primary', 'fuse-alert-type-primary' : this.type === 'primary',
'fuse-alert-type-accent': this.type === 'accent', 'fuse-alert-type-accent' : this.type === 'accent',
'fuse-alert-type-warn': this.type === 'warn', 'fuse-alert-type-warn' : this.type === 'warn',
'fuse-alert-type-basic': this.type === 'basic', 'fuse-alert-type-basic' : this.type === 'basic',
'fuse-alert-type-info': this.type === 'info', 'fuse-alert-type-info' : this.type === 'info',
'fuse-alert-type-success': this.type === 'success', 'fuse-alert-type-success' : this.type === 'success',
'fuse-alert-type-warning': this.type === 'warning', 'fuse-alert-type-warning' : this.type === 'warning',
'fuse-alert-type-error': this.type === 'error', 'fuse-alert-type-error' : this.type === 'error',
}; };
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
} }
@ -96,46 +88,46 @@ export class FuseAlertComponent implements OnChanges, OnInit, OnDestroy {
* *
* @param changes * @param changes
*/ */
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void
{
// Dismissed // Dismissed
if ('dismissed' in changes) { if ( 'dismissed' in changes )
{
// Coerce the value to a boolean // Coerce the value to a boolean
this.dismissed = coerceBooleanProperty( this.dismissed = coerceBooleanProperty(changes.dismissed.currentValue);
changes.dismissed.currentValue
);
// Dismiss/show the alert // Dismiss/show the alert
this._toggleDismiss(this.dismissed); this._toggleDismiss(this.dismissed);
} }
// Dismissible // Dismissible
if ('dismissible' in changes) { if ( 'dismissible' in changes )
{
// Coerce the value to a boolean // Coerce the value to a boolean
this.dismissible = coerceBooleanProperty( this.dismissible = coerceBooleanProperty(changes.dismissible.currentValue);
changes.dismissible.currentValue
);
} }
// Show icon // Show icon
if ('showIcon' in changes) { if ( 'showIcon' in changes )
{
// Coerce the value to a boolean // Coerce the value to a boolean
this.showIcon = coerceBooleanProperty( this.showIcon = coerceBooleanProperty(changes.showIcon.currentValue);
changes.showIcon.currentValue
);
} }
} }
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Subscribe to the dismiss calls // Subscribe to the dismiss calls
this._fuseAlertService.onDismiss this._fuseAlertService.onDismiss
.pipe( .pipe(
filter((name) => this.name === name), filter(name => this.name === name),
takeUntil(this._unsubscribeAll) takeUntil(this._unsubscribeAll),
) )
.subscribe(() => { .subscribe(() =>
{
// Dismiss the alert // Dismiss the alert
this.dismiss(); this.dismiss();
}); });
@ -143,10 +135,11 @@ export class FuseAlertComponent implements OnChanges, OnInit, OnDestroy {
// Subscribe to the show calls // Subscribe to the show calls
this._fuseAlertService.onShow this._fuseAlertService.onShow
.pipe( .pipe(
filter((name) => this.name === name), filter(name => this.name === name),
takeUntil(this._unsubscribeAll) takeUntil(this._unsubscribeAll),
) )
.subscribe(() => { .subscribe(() =>
{
// Show the alert // Show the alert
this.show(); this.show();
}); });
@ -155,7 +148,8 @@ export class FuseAlertComponent implements OnChanges, OnInit, OnDestroy {
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
this._unsubscribeAll.next(null); this._unsubscribeAll.next(null);
this._unsubscribeAll.complete(); this._unsubscribeAll.complete();
@ -168,9 +162,11 @@ export class FuseAlertComponent implements OnChanges, OnInit, OnDestroy {
/** /**
* Dismiss the alert * Dismiss the alert
*/ */
dismiss(): void { dismiss(): void
{
// Return if the alert is already dismissed // Return if the alert is already dismissed
if (this.dismissed) { if ( this.dismissed )
{
return; return;
} }
@ -181,9 +177,11 @@ export class FuseAlertComponent implements OnChanges, OnInit, OnDestroy {
/** /**
* Show the dismissed alert * Show the dismissed alert
*/ */
show(): void { show(): void
{
// Return if the alert is already showing // Return if the alert is already showing
if (!this.dismissed) { if ( !this.dismissed )
{
return; return;
} }
@ -201,9 +199,11 @@ export class FuseAlertComponent implements OnChanges, OnInit, OnDestroy {
* @param dismissed * @param dismissed
* @private * @private
*/ */
private _toggleDismiss(dismissed: boolean): void { private _toggleDismiss(dismissed: boolean): void
{
// Return if the alert is not dismissible // Return if the alert is not dismissible
if (!this.dismissible) { if ( !this.dismissible )
{
return; return;
} }

View File

@ -1,13 +1,18 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, ReplaySubject } from 'rxjs'; import { Observable, ReplaySubject } from 'rxjs';
@Injectable({ providedIn: 'root' }) @Injectable({providedIn: 'root'})
export class FuseAlertService { export class FuseAlertService
private readonly _onDismiss: ReplaySubject<string> = {
new ReplaySubject<string>(1); private readonly _onDismiss: ReplaySubject<string> = new ReplaySubject<string>(1);
private readonly _onShow: ReplaySubject<string> = new ReplaySubject<string>( private readonly _onShow: ReplaySubject<string> = new ReplaySubject<string>(1);
1
); /**
* Constructor
*/
constructor()
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Accessors // @ Accessors
@ -16,14 +21,16 @@ export class FuseAlertService {
/** /**
* Getter for onDismiss * Getter for onDismiss
*/ */
get onDismiss(): Observable<any> { get onDismiss(): Observable<any>
{
return this._onDismiss.asObservable(); return this._onDismiss.asObservable();
} }
/** /**
* Getter for onShow * Getter for onShow
*/ */
get onShow(): Observable<any> { get onShow(): Observable<any>
{
return this._onShow.asObservable(); return this._onShow.asObservable();
} }
@ -36,9 +43,11 @@ export class FuseAlertService {
* *
* @param name * @param name
*/ */
dismiss(name: string): void { dismiss(name: string): void
{
// Return if the name is not provided // Return if the name is not provided
if (!name) { if ( !name )
{
return; return;
} }
@ -51,13 +60,16 @@ export class FuseAlertService {
* *
* @param name * @param name
*/ */
show(name: string): void { show(name: string): void
{
// Return if the name is not provided // Return if the name is not provided
if (!name) { if ( !name )
{
return; return;
} }
// Execute the observable // Execute the observable
this._onShow.next(name); this._onShow.next(name);
} }
} }

View File

@ -1,4 +1,8 @@
export type FuseAlertAppearance = 'border' | 'fill' | 'outline' | 'soft'; export type FuseAlertAppearance =
| 'border'
| 'fill'
| 'outline'
| 'soft';
export type FuseAlertType = export type FuseAlertType =
| 'primary' | 'primary'

View File

@ -1,5 +1,6 @@
<!-- Flippable card --> <!-- Flippable card -->
@if (flippable) { <ng-container *ngIf="flippable">
<!-- Front --> <!-- Front -->
<div class="fuse-card-front"> <div class="fuse-card-front">
<ng-content select="[fuseCardFront]"></ng-content> <ng-content select="[fuseCardFront]"></ng-content>
@ -9,17 +10,21 @@
<div class="fuse-card-back"> <div class="fuse-card-back">
<ng-content select="[fuseCardBack]"></ng-content> <ng-content select="[fuseCardBack]"></ng-content>
</div> </div>
}
</ng-container>
<!-- Normal card --> <!-- Normal card -->
@if (!flippable) { <ng-container *ngIf="!flippable">
<!-- Content --> <!-- Content -->
<ng-content></ng-content> <ng-content></ng-content>
<!-- Expansion --> <!-- Expansion -->
@if (expanded) { <div
<div class="fuse-card-expansion" [@expandCollapse]> class="fuse-card-expansion"
<ng-content select="[fuseCardExpansion]"></ng-content> *ngIf="expanded"
</div> [@expandCollapse]>
} <ng-content select="[fuseCardExpansion]"></ng-content>
} </div>
</ng-container>

View File

@ -2,7 +2,7 @@ fuse-card {
position: relative; position: relative;
display: flex; display: flex;
overflow: hidden; overflow: hidden;
@apply bg-card rounded-2xl shadow; @apply rounded-2xl shadow bg-card;
/* Flippable */ /* Flippable */
&.fuse-card-flippable { &.fuse-card-flippable {
@ -15,6 +15,7 @@ fuse-card {
@apply shadow-none; @apply shadow-none;
&.fuse-card-face-back { &.fuse-card-face-back {
.fuse-card-front { .fuse-card-front {
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
@ -34,12 +35,9 @@ fuse-card {
flex-direction: column; flex-direction: column;
flex: 1 1 auto; flex: 1 1 auto;
z-index: 10; z-index: 10;
transition: transition: transform 0.5s ease-out 0s, visibility 0s ease-in 0.2s, opacity 0s ease-in 0.2s;
transform 0.5s ease-out 0s,
visibility 0s ease-in 0.2s,
opacity 0s ease-in 0.2s;
backface-visibility: hidden; backface-visibility: hidden;
@apply bg-card rounded-2xl shadow; @apply rounded-2xl shadow bg-card;
} }
.fuse-card-front { .fuse-card-front {

View File

@ -1,26 +1,21 @@
import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';
import { NgIf } from '@angular/common';
import { import { Component, HostBinding, Input, OnChanges, SimpleChanges, ViewEncapsulation } from '@angular/core';
Component,
HostBinding,
Input,
OnChanges,
SimpleChanges,
ViewEncapsulation,
} from '@angular/core';
import { fuseAnimations } from '@fuse/animations'; import { fuseAnimations } from '@fuse/animations';
import { FuseCardFace } from '@fuse/components/card/card.types'; import { FuseCardFace } from '@fuse/components/card/card.types';
@Component({ @Component({
selector: 'fuse-card', selector : 'fuse-card',
templateUrl: './card.component.html', templateUrl : './card.component.html',
styleUrls: ['./card.component.scss'], styleUrls : ['./card.component.scss'],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
animations: fuseAnimations, animations : fuseAnimations,
exportAs: 'fuseCard', exportAs : 'fuseCard',
imports: [], standalone : true,
imports : [NgIf],
}) })
export class FuseCardComponent implements OnChanges { export class FuseCardComponent implements OnChanges
{
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
static ngAcceptInputType_expanded: BooleanInput; static ngAcceptInputType_expanded: BooleanInput;
static ngAcceptInputType_flippable: BooleanInput; static ngAcceptInputType_flippable: BooleanInput;
@ -30,6 +25,13 @@ export class FuseCardComponent implements OnChanges {
@Input() face: FuseCardFace = 'front'; @Input() face: FuseCardFace = 'front';
@Input() flippable: boolean = false; @Input() flippable: boolean = false;
/**
* Constructor
*/
constructor()
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Accessors // @ Accessors
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -37,13 +39,14 @@ export class FuseCardComponent implements OnChanges {
/** /**
* Host binding for component classes * Host binding for component classes
*/ */
@HostBinding('class') get classList(): any { @HostBinding('class') get classList(): any
{
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
return { return {
'fuse-card-expanded': this.expanded, 'fuse-card-expanded' : this.expanded,
'fuse-card-face-back': this.flippable && this.face === 'back', 'fuse-card-face-back' : this.flippable && this.face === 'back',
'fuse-card-face-front': this.flippable && this.face === 'front', 'fuse-card-face-front': this.flippable && this.face === 'front',
'fuse-card-flippable': this.flippable, 'fuse-card-flippable' : this.flippable,
}; };
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
} }
@ -57,21 +60,20 @@ export class FuseCardComponent implements OnChanges {
* *
* @param changes * @param changes
*/ */
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void
{
// Expanded // Expanded
if ('expanded' in changes) { if ( 'expanded' in changes )
{
// Coerce the value to a boolean // Coerce the value to a boolean
this.expanded = coerceBooleanProperty( this.expanded = coerceBooleanProperty(changes.expanded.currentValue);
changes.expanded.currentValue
);
} }
// Flippable // Flippable
if ('flippable' in changes) { if ( 'flippable' in changes )
{
// Coerce the value to a boolean // Coerce the value to a boolean
this.flippable = coerceBooleanProperty( this.flippable = coerceBooleanProperty(changes.flippable.currentValue);
changes.flippable.currentValue
);
} }
} }
} }

View File

@ -1 +1,3 @@
export type FuseCardFace = 'front' | 'back'; export type FuseCardFace =
| 'front'
| 'back';

View File

@ -12,15 +12,14 @@ fuse-drawer {
min-width: var(--fuse-drawer-width); min-width: var(--fuse-drawer-width);
max-width: var(--fuse-drawer-width); max-width: var(--fuse-drawer-width);
z-index: 300; z-index: 300;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.35); box-shadow: 0 2px 8px 0 rgba(0, 0, 0, .35);
@apply bg-card; @apply bg-card;
/* Animations */ /* Animations */
&.fuse-drawer-animations-enabled { &.fuse-drawer-animations-enabled {
transition-duration: 400ms; transition-duration: 400ms;
transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1); transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1);
transition-property: visibility, margin-left, margin-right, transform, transition-property: visibility, margin-left, margin-right, transform, width, max-width, min-width;
width, max-width, min-width;
.fuse-drawer-content { .fuse-drawer-content {
transition-duration: 400ms; transition-duration: 400ms;
@ -43,6 +42,7 @@ fuse-drawer {
/* Left position */ /* Left position */
&.fuse-drawer-position-left { &.fuse-drawer-position-left {
/* Side mode */ /* Side mode */
&.fuse-drawer-mode-side { &.fuse-drawer-mode-side {
margin-left: calc(var(--fuse-drawer-width) * -1); margin-left: calc(var(--fuse-drawer-width) * -1);
@ -70,6 +70,7 @@ fuse-drawer {
/* Right position */ /* Right position */
&.fuse-drawer-position-right { &.fuse-drawer-position-right {
/* Side mode */ /* Side mode */
&.fuse-drawer-mode-side { &.fuse-drawer-mode-side {
margin-right: calc(var(--fuse-drawer-width) * -1); margin-right: calc(var(--fuse-drawer-width) * -1);

View File

@ -1,75 +1,60 @@
import { import { animate, AnimationBuilder, AnimationPlayer, style } from '@angular/animations';
animate,
AnimationBuilder,
AnimationPlayer,
style,
} from '@angular/animations';
import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';
import { import { Component, ElementRef, EventEmitter, HostBinding, HostListener, Input, OnChanges, OnDestroy, OnInit, Output, Renderer2, SimpleChanges, ViewEncapsulation } from '@angular/core';
Component,
ElementRef,
EventEmitter,
HostBinding,
HostListener,
inject,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
Renderer2,
SimpleChanges,
ViewEncapsulation,
} from '@angular/core';
import { FuseDrawerService } from '@fuse/components/drawer/drawer.service'; import { FuseDrawerService } from '@fuse/components/drawer/drawer.service';
import { import { FuseDrawerMode, FuseDrawerPosition } from '@fuse/components/drawer/drawer.types';
FuseDrawerMode,
FuseDrawerPosition,
} from '@fuse/components/drawer/drawer.types';
import { FuseUtilsService } from '@fuse/services/utils/utils.service'; import { FuseUtilsService } from '@fuse/services/utils/utils.service';
@Component({ @Component({
selector: 'fuse-drawer', selector : 'fuse-drawer',
templateUrl: './drawer.component.html', templateUrl : './drawer.component.html',
styleUrls: ['./drawer.component.scss'], styleUrls : ['./drawer.component.scss'],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
exportAs: 'fuseDrawer', exportAs : 'fuseDrawer',
standalone: true, standalone : true,
}) })
export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy { export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy
{
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
static ngAcceptInputType_fixed: BooleanInput; static ngAcceptInputType_fixed: BooleanInput;
static ngAcceptInputType_opened: BooleanInput; static ngAcceptInputType_opened: BooleanInput;
static ngAcceptInputType_transparentOverlay: BooleanInput; static ngAcceptInputType_transparentOverlay: BooleanInput;
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
private _animationBuilder = inject(AnimationBuilder);
private _elementRef = inject(ElementRef);
private _renderer2 = inject(Renderer2);
private _fuseDrawerService = inject(FuseDrawerService);
private _fuseUtilsService = inject(FuseUtilsService);
@Input() fixed: boolean = false; @Input() fixed: boolean = false;
@Input() mode: FuseDrawerMode = 'side'; @Input() mode: FuseDrawerMode = 'side';
@Input() name: string = this._fuseUtilsService.randomId(); @Input() name: string = this._fuseUtilsService.randomId();
@Input() opened: boolean = false; @Input() opened: boolean = false;
@Input() position: FuseDrawerPosition = 'left'; @Input() position: FuseDrawerPosition = 'left';
@Input() transparentOverlay: boolean = false; @Input() transparentOverlay: boolean = false;
@Output() readonly fixedChanged: EventEmitter<boolean> = @Output() readonly fixedChanged: EventEmitter<boolean> = new EventEmitter<boolean>();
new EventEmitter<boolean>(); @Output() readonly modeChanged: EventEmitter<FuseDrawerMode> = new EventEmitter<FuseDrawerMode>();
@Output() readonly modeChanged: EventEmitter<FuseDrawerMode> = @Output() readonly openedChanged: EventEmitter<boolean> = new EventEmitter<boolean>();
new EventEmitter<FuseDrawerMode>(); @Output() readonly positionChanged: EventEmitter<FuseDrawerPosition> = new EventEmitter<FuseDrawerPosition>();
@Output() readonly openedChanged: EventEmitter<boolean> =
new EventEmitter<boolean>();
@Output() readonly positionChanged: EventEmitter<FuseDrawerPosition> =
new EventEmitter<FuseDrawerPosition>();
private _animationsEnabled: boolean = false; private _animationsEnabled: boolean = false;
private readonly _handleOverlayClick = (): void => this.close(); private readonly _handleOverlayClick: any;
private _hovered: boolean = false; private _hovered: boolean = false;
private _overlay: HTMLElement; private _overlay: HTMLElement;
private _player: AnimationPlayer; private _player: AnimationPlayer;
/**
* Constructor
*/
constructor(
private _animationBuilder: AnimationBuilder,
private _elementRef: ElementRef,
private _renderer2: Renderer2,
private _fuseDrawerService: FuseDrawerService,
private _fuseUtilsService: FuseUtilsService,
)
{
this._handleOverlayClick = (): void =>
{
this.close();
};
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Accessors // @ Accessors
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -77,14 +62,15 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
/** /**
* Host binding for component classes * Host binding for component classes
*/ */
@HostBinding('class') get classList(): any { @HostBinding('class') get classList(): any
{
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
return { return {
'fuse-drawer-animations-enabled': this._animationsEnabled, 'fuse-drawer-animations-enabled' : this._animationsEnabled,
'fuse-drawer-fixed': this.fixed, 'fuse-drawer-fixed' : this.fixed,
'fuse-drawer-hover': this._hovered, 'fuse-drawer-hover' : this._hovered,
[`fuse-drawer-mode-${this.mode}`]: true, [`fuse-drawer-mode-${this.mode}`] : true,
'fuse-drawer-opened': this.opened, 'fuse-drawer-opened' : this.opened,
[`fuse-drawer-position-${this.position}`]: true, [`fuse-drawer-position-${this.position}`]: true,
}; };
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
@ -93,9 +79,10 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
/** /**
* Host binding for component inline styles * Host binding for component inline styles
*/ */
@HostBinding('style') get styleList(): any { @HostBinding('style') get styleList(): any
{
return { return {
visibility: this.opened ? 'visible' : 'hidden', 'visibility': this.opened ? 'visible' : 'hidden',
}; };
} }
@ -109,7 +96,8 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
* @private * @private
*/ */
@HostListener('mouseenter') @HostListener('mouseenter')
private _onMouseenter(): void { private _onMouseenter(): void
{
// Enable the animations // Enable the animations
this._enableAnimations(); this._enableAnimations();
@ -123,7 +111,8 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
* @private * @private
*/ */
@HostListener('mouseleave') @HostListener('mouseleave')
private _onMouseleave(): void { private _onMouseleave(): void
{
// Enable the animations // Enable the animations
this._enableAnimations(); this._enableAnimations();
@ -140,9 +129,11 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
* *
* @param changes * @param changes
*/ */
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void
{
// Fixed // Fixed
if ('fixed' in changes) { if ( 'fixed' in changes )
{
// Coerce the value to a boolean // Coerce the value to a boolean
this.fixed = coerceBooleanProperty(changes.fixed.currentValue); this.fixed = coerceBooleanProperty(changes.fixed.currentValue);
@ -151,7 +142,8 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
} }
// Mode // Mode
if ('mode' in changes) { if ( 'mode' in changes )
{
// Get the previous and current values // Get the previous and current values
const previousMode = changes.mode.previousValue; const previousMode = changes.mode.previousValue;
const currentMode = changes.mode.currentValue; const currentMode = changes.mode.currentValue;
@ -160,15 +152,18 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
this._disableAnimations(); this._disableAnimations();
// If the mode changes: 'over -> side' // If the mode changes: 'over -> side'
if (previousMode === 'over' && currentMode === 'side') { if ( previousMode === 'over' && currentMode === 'side' )
{
// Hide the overlay // Hide the overlay
this._hideOverlay(); this._hideOverlay();
} }
// If the mode changes: 'side -> over' // If the mode changes: 'side -> over'
if (previousMode === 'side' && currentMode === 'over') { if ( previousMode === 'side' && currentMode === 'over' )
{
// If the drawer is opened // If the drawer is opened
if (this.opened) { if ( this.opened )
{
// Show the overlay // Show the overlay
this._showOverlay(); this._showOverlay();
} }
@ -180,13 +175,15 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
// Enable the animations after a delay // Enable the animations after a delay
// The delay must be bigger than the current transition-duration // The delay must be bigger than the current transition-duration
// to make sure nothing will be animated while the mode is changing // to make sure nothing will be animated while the mode is changing
setTimeout(() => { setTimeout(() =>
{
this._enableAnimations(); this._enableAnimations();
}, 500); }, 500);
} }
// Opened // Opened
if ('opened' in changes) { if ( 'opened' in changes )
{
// Coerce the value to a boolean // Coerce the value to a boolean
const open = coerceBooleanProperty(changes.opened.currentValue); const open = coerceBooleanProperty(changes.opened.currentValue);
@ -195,24 +192,25 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
} }
// Position // Position
if ('position' in changes) { if ( 'position' in changes )
{
// Execute the observable // Execute the observable
this.positionChanged.next(this.position); this.positionChanged.next(this.position);
} }
// Transparent overlay // Transparent overlay
if ('transparentOverlay' in changes) { if ( 'transparentOverlay' in changes )
{
// Coerce the value to a boolean // Coerce the value to a boolean
this.transparentOverlay = coerceBooleanProperty( this.transparentOverlay = coerceBooleanProperty(changes.transparentOverlay.currentValue);
changes.transparentOverlay.currentValue
);
} }
} }
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Register the drawer // Register the drawer
this._fuseDrawerService.registerComponent(this.name, this); this._fuseDrawerService.registerComponent(this.name, this);
} }
@ -220,9 +218,11 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
// Finish the animation // Finish the animation
if (this._player) { if ( this._player )
{
this._player.finish(); this._player.finish();
} }
@ -237,9 +237,11 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
/** /**
* Open the drawer * Open the drawer
*/ */
open(): void { open(): void
{
// Return if the drawer has already opened // Return if the drawer has already opened
if (this.opened) { if ( this.opened )
{
return; return;
} }
@ -250,9 +252,11 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
/** /**
* Close the drawer * Close the drawer
*/ */
close(): void { close(): void
{
// Return if the drawer has already closed // Return if the drawer has already closed
if (!this.opened) { if ( !this.opened )
{
return; return;
} }
@ -263,10 +267,14 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
/** /**
* Toggle the drawer * Toggle the drawer
*/ */
toggle(): void { toggle(): void
if (this.opened) { {
if ( this.opened )
{
this.close(); this.close();
} else { }
else
{
this.open(); this.open();
} }
} }
@ -280,9 +288,11 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
* *
* @private * @private
*/ */
private _enableAnimations(): void { private _enableAnimations(): void
{
// Return if the animations are already enabled // Return if the animations are already enabled
if (this._animationsEnabled) { if ( this._animationsEnabled )
{
return; return;
} }
@ -295,9 +305,11 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
* *
* @private * @private
*/ */
private _disableAnimations(): void { private _disableAnimations(): void
{
// Return if the animations are already disabled // Return if the animations are already disabled
if (!this._animationsEnabled) { if ( !this._animationsEnabled )
{
return; return;
} }
@ -310,7 +322,8 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
* *
* @private * @private
*/ */
private _showOverlay(): void { private _showOverlay(): void
{
// Create the backdrop element // Create the backdrop element
this._overlay = this._renderer2.createElement('div'); this._overlay = this._renderer2.createElement('div');
@ -318,31 +331,25 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
this._overlay.classList.add('fuse-drawer-overlay'); this._overlay.classList.add('fuse-drawer-overlay');
// Add a class depending on the fixed option // Add a class depending on the fixed option
if (this.fixed) { if ( this.fixed )
{
this._overlay.classList.add('fuse-drawer-overlay-fixed'); this._overlay.classList.add('fuse-drawer-overlay-fixed');
} }
// Add a class depending on the transparentOverlay option // Add a class depending on the transparentOverlay option
if (this.transparentOverlay) { if ( this.transparentOverlay )
{
this._overlay.classList.add('fuse-drawer-overlay-transparent'); this._overlay.classList.add('fuse-drawer-overlay-transparent');
} }
// Append the backdrop to the parent of the drawer // Append the backdrop to the parent of the drawer
this._renderer2.appendChild( this._renderer2.appendChild(this._elementRef.nativeElement.parentElement, this._overlay);
this._elementRef.nativeElement.parentElement,
this._overlay
);
// Create enter animation and attach it to the player // Create enter animation and attach it to the player
this._player = this._animationBuilder this._player = this._animationBuilder.build([
.build([ style({opacity: 0}),
style({ opacity: 0 }), animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({opacity: 1})),
animate( ]).create(this._overlay);
'300ms cubic-bezier(0.25, 0.8, 0.25, 1)',
style({ opacity: 1 })
),
])
.create(this._overlay);
// Play the animation // Play the animation
this._player.play(); this._player.play();
@ -356,33 +363,29 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
* *
* @private * @private
*/ */
private _hideOverlay(): void { private _hideOverlay(): void
if (!this._overlay) { {
if ( !this._overlay )
{
return; return;
} }
// Create the leave animation and attach it to the player // Create the leave animation and attach it to the player
this._player = this._animationBuilder this._player = this._animationBuilder.build([
.build([ animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({opacity: 0})),
animate( ]).create(this._overlay);
'300ms cubic-bezier(0.25, 0.8, 0.25, 1)',
style({ opacity: 0 })
),
])
.create(this._overlay);
// Play the animation // Play the animation
this._player.play(); this._player.play();
// Once the animation is done... // Once the animation is done...
this._player.onDone(() => { this._player.onDone(() =>
{
// If the overlay still exists... // If the overlay still exists...
if (this._overlay) { if ( this._overlay )
{
// Remove the event listener // Remove the event listener
this._overlay.removeEventListener( this._overlay.removeEventListener('click', this._handleOverlayClick);
'click',
this._handleOverlayClick
);
// Remove the overlay // Remove the overlay
this._overlay.parentNode.removeChild(this._overlay); this._overlay.parentNode.removeChild(this._overlay);
@ -397,7 +400,8 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
* @param open * @param open
* @private * @private
*/ */
private _toggleOpened(open: boolean): void { private _toggleOpened(open: boolean): void
{
// Set the opened // Set the opened
this.opened = open; this.opened = open;
@ -405,13 +409,16 @@ export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
this._enableAnimations(); this._enableAnimations();
// If the mode is 'over' // If the mode is 'over'
if (this.mode === 'over') { if ( this.mode === 'over' )
{
// If the drawer opens, show the overlay // If the drawer opens, show the overlay
if (open) { if ( open )
{
this._showOverlay(); this._showOverlay();
} }
// Otherwise, close the overlay // Otherwise, close the overlay
else { else
{
this._hideOverlay(); this._hideOverlay();
} }
} }

View File

@ -1,12 +1,17 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { FuseDrawerComponent } from '@fuse/components/drawer/drawer.component'; import { FuseDrawerComponent } from '@fuse/components/drawer/drawer.component';
@Injectable({ providedIn: 'root' }) @Injectable({providedIn: 'root'})
export class FuseDrawerService { export class FuseDrawerService
private _componentRegistry: Map<string, FuseDrawerComponent> = new Map< {
string, private _componentRegistry: Map<string, FuseDrawerComponent> = new Map<string, FuseDrawerComponent>();
FuseDrawerComponent
>(); /**
* Constructor
*/
constructor()
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Public methods // @ Public methods
@ -18,7 +23,8 @@ export class FuseDrawerService {
* @param name * @param name
* @param component * @param component
*/ */
registerComponent(name: string, component: FuseDrawerComponent): void { registerComponent(name: string, component: FuseDrawerComponent): void
{
this._componentRegistry.set(name, component); this._componentRegistry.set(name, component);
} }
@ -27,7 +33,8 @@ export class FuseDrawerService {
* *
* @param name * @param name
*/ */
deregisterComponent(name: string): void { deregisterComponent(name: string): void
{
this._componentRegistry.delete(name); this._componentRegistry.delete(name);
} }
@ -36,7 +43,8 @@ export class FuseDrawerService {
* *
* @param name * @param name
*/ */
getComponent(name: string): FuseDrawerComponent | undefined { getComponent(name: string): FuseDrawerComponent | undefined
{
return this._componentRegistry.get(name); return this._componentRegistry.get(name);
} }
} }

View File

@ -1,3 +1,7 @@
export type FuseDrawerMode = 'over' | 'side'; export type FuseDrawerMode =
| 'over'
| 'side';
export type FuseDrawerPosition = 'left' | 'right'; export type FuseDrawerPosition =
| 'left'
| 'right';

View File

@ -2,8 +2,7 @@
<button <button
mat-icon-button mat-icon-button
[matTooltip]="tooltip || 'Toggle Fullscreen'" [matTooltip]="tooltip || 'Toggle Fullscreen'"
(click)="toggleFullscreen()" (click)="toggleFullscreen()">
>
<ng-container [ngTemplateOutlet]="iconTpl || defaultIconTpl"></ng-container> <ng-container [ngTemplateOutlet]="iconTpl || defaultIconTpl"></ng-container>
</button> </button>

View File

@ -1,34 +1,46 @@
import { DOCUMENT, NgTemplateOutlet } from '@angular/common'; import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
import { import { ChangeDetectionStrategy, Component, Inject, Input, OnInit, TemplateRef, ViewEncapsulation } from '@angular/core';
ChangeDetectionStrategy,
Component,
Input,
TemplateRef,
ViewEncapsulation,
inject,
} from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { FSDocument, FSDocumentElement } from '@fuse/components/fullscreen/fullscreen.types';
@Component({ @Component({
selector: 'fuse-fullscreen', selector : 'fuse-fullscreen',
templateUrl: './fullscreen.component.html', templateUrl : './fullscreen.component.html',
encapsulation: ViewEncapsulation.None, encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'fuseFullscreen', exportAs : 'fuseFullscreen',
imports: [ standalone : true,
MatButtonModule, imports : [MatButtonModule, MatTooltipModule, NgTemplateOutlet, MatIconModule],
MatTooltipModule,
NgTemplateOutlet,
MatIconModule,
],
}) })
export class FuseFullscreenComponent { export class FuseFullscreenComponent implements OnInit
private _document = inject(DOCUMENT); {
@Input() iconTpl: TemplateRef<any>; @Input() iconTpl: TemplateRef<any>;
@Input() tooltip: string; @Input() tooltip: string;
private _fsDoc: FSDocument;
private _fsDocEl: FSDocumentElement;
private _isFullscreen: boolean = false;
/**
* Constructor
*/
constructor(@Inject(DOCUMENT) private _document: Document)
{
this._fsDoc = _document as FSDocument;
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void
{
this._fsDocEl = document.documentElement as FSDocumentElement;
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Public methods // @ Public methods
@ -37,22 +49,123 @@ export class FuseFullscreenComponent {
/** /**
* Toggle the fullscreen mode * Toggle the fullscreen mode
*/ */
toggleFullscreen(): void { toggleFullscreen(): void
if (!this._document.fullscreenEnabled) { {
console.log('Fullscreen is not available in this browser.'); // Check if the fullscreen is open
this._isFullscreen = this._getBrowserFullscreenElement() !== null;
// Toggle the fullscreen
if ( this._isFullscreen )
{
this._closeFullscreen();
}
else
{
this._openFullscreen();
}
}
// -----------------------------------------------------------------------------------------------------
// @ Private methods
// -----------------------------------------------------------------------------------------------------
/**
* Get browser's fullscreen element
*
* @private
*/
private _getBrowserFullscreenElement(): Element
{
if ( typeof this._fsDoc.fullscreenElement !== 'undefined' )
{
return this._fsDoc.fullscreenElement;
}
if ( typeof this._fsDoc.mozFullScreenElement !== 'undefined' )
{
return this._fsDoc.mozFullScreenElement;
}
if ( typeof this._fsDoc.msFullscreenElement !== 'undefined' )
{
return this._fsDoc.msFullscreenElement;
}
if ( typeof this._fsDoc.webkitFullscreenElement !== 'undefined' )
{
return this._fsDoc.webkitFullscreenElement;
}
throw new Error('Fullscreen mode is not supported by this browser');
}
/**
* Open the fullscreen
*
* @private
*/
private _openFullscreen(): void
{
if ( this._fsDocEl.requestFullscreen )
{
this._fsDocEl.requestFullscreen();
return; return;
} }
// Check if the fullscreen is already open // Firefox
const fullScreen = this._document.fullscreenElement; if ( this._fsDocEl.mozRequestFullScreen )
{
this._fsDocEl.mozRequestFullScreen();
return;
}
// Toggle the fullscreen // Chrome, Safari and Opera
if (fullScreen) { if ( this._fsDocEl.webkitRequestFullscreen )
this._document.exitFullscreen(); {
} else { this._fsDocEl.webkitRequestFullscreen();
this._document.documentElement.requestFullscreen().catch(() => { return;
console.error('Entering fullscreen mode failed.'); }
});
// IE/Edge
if ( this._fsDocEl.msRequestFullscreen )
{
this._fsDocEl.msRequestFullscreen();
return;
}
}
/**
* Close the fullscreen
*
* @private
*/
private _closeFullscreen(): void
{
if ( this._fsDoc.exitFullscreen )
{
this._fsDoc.exitFullscreen();
return;
}
// Firefox
if ( this._fsDoc.mozCancelFullScreen )
{
this._fsDoc.mozCancelFullScreen();
return;
}
// Chrome, Safari and Opera
if ( this._fsDoc.webkitExitFullscreen )
{
this._fsDoc.webkitExitFullscreen();
return;
}
// IE/Edge
else if ( this._fsDoc.msExitFullscreen )
{
this._fsDoc.msExitFullscreen();
return;
} }
} }
} }

View File

@ -0,0 +1,16 @@
export interface FSDocument extends HTMLDocument
{
mozFullScreenElement?: Element;
mozCancelFullScreen?: () => void;
msFullscreenElement?: Element;
msExitFullscreen?: () => void;
webkitFullscreenElement?: Element;
webkitExitFullscreen?: () => void;
}
export interface FSDocumentElement extends HTMLElement
{
mozRequestFullScreen?: () => void;
msRequestFullscreen?: () => void;
webkitRequestFullscreen?: () => void;
}

View File

@ -1 +1,2 @@
export * from '@fuse/components/fullscreen/fullscreen.component'; export * from '@fuse/components/fullscreen/fullscreen.component';
export * from '@fuse/components/fullscreen/fullscreen.types';

View File

@ -1,8 +1,9 @@
<ng-content></ng-content> <ng-content></ng-content>
<!-- prettier-ignore --> <!-- @formatter:off -->
<ng-template let-highlightedCode="highlightedCode" let-lang="lang"> <ng-template let-highlightedCode="highlightedCode" let-lang="lang">
<div class="fuse-highlight fuse-highlight-code-container"> <div class="fuse-highlight fuse-highlight-code-container">
<pre [ngClass]="'language-' + lang"><code [ngClass]="'language-' + lang" [innerHTML]="highlightedCode"></code></pre> <pre [ngClass]="'language-' + lang"><code [ngClass]="'language-' + lang" [innerHTML]="highlightedCode"></code></pre>
</div> </div>
</ng-template> </ng-template>
<!-- @formatter:on -->

View File

@ -1,38 +1,20 @@
import { NgClass } from '@angular/common'; import { NgClass } from '@angular/common';
import { import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EmbeddedViewRef, Input, OnChanges, Renderer2, SecurityContext, SimpleChanges, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
AfterViewInit,
ChangeDetectionStrategy,
Component,
ElementRef,
EmbeddedViewRef,
inject,
Input,
OnChanges,
SecurityContext,
SimpleChanges,
TemplateRef,
ViewChild,
ViewContainerRef,
ViewEncapsulation,
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { FuseHighlightService } from '@fuse/components/highlight/highlight.service'; import { FuseHighlightService } from '@fuse/components/highlight/highlight.service';
@Component({ @Component({
selector: 'textarea[fuse-highlight]', selector : 'textarea[fuse-highlight]',
templateUrl: './highlight.component.html', templateUrl : './highlight.component.html',
styleUrls: ['./highlight.component.scss'], styleUrls : ['./highlight.component.scss'],
encapsulation: ViewEncapsulation.None, encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'fuseHighlight', exportAs : 'fuseHighlight',
imports: [NgClass], standalone : true,
imports : [NgClass],
}) })
export class FuseHighlightComponent implements OnChanges, AfterViewInit { export class FuseHighlightComponent implements OnChanges, AfterViewInit
private _domSanitizer = inject(DomSanitizer); {
private _elementRef = inject(ElementRef);
private _fuseHighlightService = inject(FuseHighlightService);
private _viewContainerRef = inject(ViewContainerRef);
@Input() code: string; @Input() code: string;
@Input() lang: string; @Input() lang: string;
@ViewChild(TemplateRef) templateRef: TemplateRef<any>; @ViewChild(TemplateRef) templateRef: TemplateRef<any>;
@ -40,6 +22,20 @@ export class FuseHighlightComponent implements OnChanges, AfterViewInit {
highlightedCode: string; highlightedCode: string;
private _viewRef: EmbeddedViewRef<any>; private _viewRef: EmbeddedViewRef<any>;
/**
* Constructor
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _domSanitizer: DomSanitizer,
private _elementRef: ElementRef,
private _renderer2: Renderer2,
private _fuseHighlightService: FuseHighlightService,
private _viewContainerRef: ViewContainerRef,
)
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks // @ Lifecycle hooks
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -49,11 +45,14 @@ export class FuseHighlightComponent implements OnChanges, AfterViewInit {
* *
* @param changes * @param changes
*/ */
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void
{
// Code & Lang // Code & Lang
if ('code' in changes || 'lang' in changes) { if ( 'code' in changes || 'lang' in changes )
{
// Return if the viewContainerRef is not available // Return if the viewContainerRef is not available
if (!this._viewContainerRef.length) { if ( !this._viewContainerRef.length )
{
return; return;
} }
@ -65,15 +64,18 @@ export class FuseHighlightComponent implements OnChanges, AfterViewInit {
/** /**
* After view init * After view init
*/ */
ngAfterViewInit(): void { ngAfterViewInit(): void
{
// Return if there is no language set // Return if there is no language set
if (!this.lang) { if ( !this.lang )
{
return; return;
} }
// If there is no code input, get the code from // If there is no code input, get the code from
// the textarea // the textarea
if (!this.code) { if ( !this.code )
{
// Get the code // Get the code
this.code = this._elementRef.nativeElement.value; this.code = this._elementRef.nativeElement.value;
} }
@ -91,42 +93,41 @@ export class FuseHighlightComponent implements OnChanges, AfterViewInit {
* *
* @private * @private
*/ */
private _highlightAndInsert(): void { private _highlightAndInsert(): void
{
// Return if the template reference is not available // Return if the template reference is not available
if (!this.templateRef) { if ( !this.templateRef )
{
return; return;
} }
// Return if the code or language is not defined // Return if the code or language is not defined
if (!this.code || !this.lang) { if ( !this.code || !this.lang )
{
return; return;
} }
// Destroy the component if there is already one // Destroy the component if there is already one
if (this._viewRef) { if ( this._viewRef )
{
this._viewRef.destroy(); this._viewRef.destroy();
this._viewRef = null; this._viewRef = null;
} }
// Highlight and sanitize the code just in case // Highlight and sanitize the code just in case
this.highlightedCode = this._domSanitizer.sanitize( this.highlightedCode = this._domSanitizer.sanitize(SecurityContext.HTML, this._fuseHighlightService.highlight(this.code, this.lang));
SecurityContext.HTML,
this._fuseHighlightService.highlight(this.code, this.lang)
);
// Return if the highlighted code is null // Return if the highlighted code is null
if (this.highlightedCode === null) { if ( this.highlightedCode === null )
{
return; return;
} }
// Render and insert the template // Render and insert the template
this._viewRef = this._viewContainerRef.createEmbeddedView( this._viewRef = this._viewContainerRef.createEmbeddedView(this.templateRef, {
this.templateRef, highlightedCode: this.highlightedCode,
{ lang : this.lang,
highlightedCode: this.highlightedCode, });
lang: this.lang,
}
);
// Detect the changes // Detect the changes
this._viewRef.detectChanges(); this._viewRef.detectChanges();

View File

@ -1,8 +1,16 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import hljs from 'highlight.js'; import hljs from 'highlight.js';
@Injectable({ providedIn: 'root' }) @Injectable({providedIn: 'root'})
export class FuseHighlightService { export class FuseHighlightService
{
/**
* Constructor
*/
constructor()
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Public methods // @ Public methods
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -10,12 +18,13 @@ export class FuseHighlightService {
/** /**
* Highlight * Highlight
*/ */
highlight(code: string, language: string): string { highlight(code: string, language: string): string
{
// Format the code // Format the code
code = this._format(code); code = this._format(code);
// Highlight and return the code // Highlight and return the code
return hljs.highlight(code, { language }).value; return hljs.highlight(code, {language}).value;
} }
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -30,28 +39,32 @@ export class FuseHighlightService {
* @param code * @param code
* @private * @private
*/ */
private _format(code: string): string { private _format(code: string): string
{
let indentation = 0; let indentation = 0;
// Split the code into lines and store the lines // Split the code into lines and store the lines
const lines = code.split('\n'); const lines = code.split('\n');
// Trim the empty lines around the code block // Trim the empty lines around the code block
while (lines.length && lines[0].trim() === '') { while ( lines.length && lines[0].trim() === '' )
{
lines.shift(); lines.shift();
} }
while (lines.length && lines[lines.length - 1].trim() === '') { while ( lines.length && lines[lines.length - 1].trim() === '' )
{
lines.pop(); lines.pop();
} }
// Iterate through the lines // Iterate through the lines
lines lines.filter(line => line.length)
.filter((line) => line.length) .forEach((line, index) =>
.forEach((line, index) => { {
// Always get the indentation of the first line so we can // Always get the indentation of the first line so we can
// have something to compare with // have something to compare with
if (index === 0) { if ( index === 0 )
{
indentation = line.search(/\S|$/); indentation = line.search(/\S|$/);
return; return;
} }
@ -62,6 +75,6 @@ export class FuseHighlightService {
// Iterate through the lines one more time, remove the extra // Iterate through the lines one more time, remove the extra
// indentation, join them together and return it // indentation, join them together and return it
return lines.map((line) => line.substring(indentation)).join('\n'); return lines.map(line => line.substring(indentation)).join('\n');
} }
} }

View File

@ -1,3 +1,5 @@
@if (show) { <ng-container *ngIf="show">
<mat-progress-bar [mode]="mode" [value]="progress"></mat-progress-bar> <mat-progress-bar
} [mode]="mode"
[value]="progress"></mat-progress-bar>
</ng-container>

View File

@ -1,36 +1,34 @@
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { NgIf } from '@angular/common';
import { import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core';
Component,
inject,
Input,
OnChanges,
OnDestroy,
OnInit,
SimpleChanges,
ViewEncapsulation,
} from '@angular/core';
import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatProgressBarModule } from '@angular/material/progress-bar';
import { FuseLoadingService } from '@fuse/services/loading'; import { FuseLoadingService } from '@fuse/services/loading';
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'fuse-loading-bar', selector : 'fuse-loading-bar',
templateUrl: './loading-bar.component.html', templateUrl : './loading-bar.component.html',
styleUrls: ['./loading-bar.component.scss'], styleUrls : ['./loading-bar.component.scss'],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
exportAs: 'fuseLoadingBar', exportAs : 'fuseLoadingBar',
imports: [MatProgressBarModule], standalone : true,
imports : [NgIf, MatProgressBarModule],
}) })
export class FuseLoadingBarComponent implements OnChanges, OnInit, OnDestroy { export class FuseLoadingBarComponent implements OnChanges, OnInit, OnDestroy
private _fuseLoadingService = inject(FuseLoadingService); {
@Input() autoMode: boolean = true; @Input() autoMode: boolean = true;
mode: 'determinate' | 'indeterminate'; mode: 'determinate' | 'indeterminate';
progress: number = 0; progress: number = 0;
show: boolean = false; show: boolean = false;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(private _fuseLoadingService: FuseLoadingService)
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks // @ Lifecycle hooks
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -40,44 +38,50 @@ export class FuseLoadingBarComponent implements OnChanges, OnInit, OnDestroy {
* *
* @param changes * @param changes
*/ */
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void
{
// Auto mode // Auto mode
if ('autoMode' in changes) { if ( 'autoMode' in changes )
{
// Set the auto mode in the service // Set the auto mode in the service
this._fuseLoadingService.setAutoMode( this._fuseLoadingService.setAutoMode(coerceBooleanProperty(changes.autoMode.currentValue));
coerceBooleanProperty(changes.autoMode.currentValue)
);
} }
} }
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Subscribe to the service // Subscribe to the service
this._fuseLoadingService.mode$ this._fuseLoadingService.mode$
.pipe(takeUntil(this._unsubscribeAll)) .pipe(takeUntil(this._unsubscribeAll))
.subscribe((value) => { .subscribe((value) =>
{
this.mode = value; this.mode = value;
}); });
this._fuseLoadingService.progress$ this._fuseLoadingService.progress$
.pipe(takeUntil(this._unsubscribeAll)) .pipe(takeUntil(this._unsubscribeAll))
.subscribe((value) => { .subscribe((value) =>
{
this.progress = value; this.progress = value;
}); });
this._fuseLoadingService.show$ this._fuseLoadingService.show$
.pipe(takeUntil(this._unsubscribeAll)) .pipe(takeUntil(this._unsubscribeAll))
.subscribe((value) => { .subscribe((value) =>
{
this.show = value; this.show = value;
}); });
} }
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
this._unsubscribeAll.next(null); this._unsubscribeAll.next(null);
this._unsubscribeAll.complete(); this._unsubscribeAll.complete();

View File

@ -1,8 +1,3 @@
<div class="flex"> <div class="flex">
<ng-container <ng-container *ngTemplateOutlet="columnsTemplate; context: { $implicit: distributedColumns }"></ng-container>
*ngTemplateOutlet="
columnsTemplate;
context: { $implicit: distributedColumns }
"
></ng-container>
</div> </div>

View File

@ -1,29 +1,31 @@
import { NgTemplateOutlet } from '@angular/common'; import { NgTemplateOutlet } from '@angular/common';
import { import { AfterViewInit, Component, Input, OnChanges, SimpleChanges, TemplateRef, ViewEncapsulation } from '@angular/core';
AfterViewInit,
Component,
Input,
OnChanges,
SimpleChanges,
TemplateRef,
ViewEncapsulation,
} from '@angular/core';
import { fuseAnimations } from '@fuse/animations'; import { fuseAnimations } from '@fuse/animations';
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
@Component({ @Component({
selector: 'fuse-masonry', selector : 'fuse-masonry',
templateUrl: './masonry.component.html', templateUrl : './masonry.component.html',
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
animations: fuseAnimations, animations : fuseAnimations,
exportAs: 'fuseMasonry', exportAs : 'fuseMasonry',
imports: [NgTemplateOutlet], standalone : true,
imports : [NgTemplateOutlet],
}) })
export class FuseMasonryComponent implements OnChanges, AfterViewInit { export class FuseMasonryComponent implements OnChanges, AfterViewInit
{
@Input() columnsTemplate: TemplateRef<any>; @Input() columnsTemplate: TemplateRef<any>;
@Input() columns: number; @Input() columns: number;
@Input() items: any[] = []; @Input() items: any[] = [];
distributedColumns: any[] = []; distributedColumns: any[] = [];
/**
* Constructor
*/
constructor(private _fuseMediaWatcherService: FuseMediaWatcherService)
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks // @ Lifecycle hooks
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -33,15 +35,18 @@ export class FuseMasonryComponent implements OnChanges, AfterViewInit {
* *
* @param changes * @param changes
*/ */
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void
{
// Columns // Columns
if ('columns' in changes) { if ( 'columns' in changes )
{
// Distribute the items // Distribute the items
this._distributeItems(); this._distributeItems();
} }
// Items // Items
if ('items' in changes) { if ( 'items' in changes )
{
// Distribute the items // Distribute the items
this._distributeItems(); this._distributeItems();
} }
@ -50,7 +55,8 @@ export class FuseMasonryComponent implements OnChanges, AfterViewInit {
/** /**
* After view init * After view init
*/ */
ngAfterViewInit(): void { ngAfterViewInit(): void
{
// Distribute the items for the first time // Distribute the items for the first time
this._distributeItems(); this._distributeItems();
} }
@ -62,20 +68,21 @@ export class FuseMasonryComponent implements OnChanges, AfterViewInit {
/** /**
* Distribute items into columns * Distribute items into columns
*/ */
private _distributeItems(): void { private _distributeItems(): void
{
// Return an empty array if there are no items // Return an empty array if there are no items
if (this.items.length === 0) { if ( this.items.length === 0 )
{
this.distributedColumns = []; this.distributedColumns = [];
return; return;
} }
// Prepare the distributed columns array // Prepare the distributed columns array
this.distributedColumns = Array.from(Array(this.columns), (item) => ({ this.distributedColumns = Array.from(Array(this.columns), item => ({items: []}));
items: [],
}));
// Distribute the items to columns // Distribute the items to columns
for (let i = 0; i < this.items.length; i++) { for ( let i = 0; i < this.items.length; i++ )
{
this.distributedColumns[i % this.columns].items.push(this.items[i]); this.distributedColumns[i % this.columns].items.push(this.items[i]);
} }
} }

View File

@ -2,15 +2,13 @@
<div <div
class="fuse-horizontal-navigation-item-wrapper" class="fuse-horizontal-navigation-item-wrapper"
[class.fuse-horizontal-navigation-item-has-subtitle]="!!item.subtitle" [class.fuse-horizontal-navigation-item-has-subtitle]="!!item.subtitle"
[ngClass]="item.classes?.wrapper" [ngClass]="item.classes?.wrapper">
>
<!-- Item with an internal link --> <!-- Item with an internal link -->
@if (item.link && !item.externalLink && !item.function && !item.disabled) { <ng-container *ngIf="item.link && !item.externalLink && !item.function && !item.disabled">
<div <div
class="fuse-horizontal-navigation-item" class="fuse-horizontal-navigation-item"
[ngClass]="{ [ngClass]="{'fuse-horizontal-navigation-item-active-forced': item.active}"
'fuse-horizontal-navigation-item-active-forced': item.active,
}"
[routerLink]="[item.link]" [routerLink]="[item.link]"
[fragment]="item.fragment ?? null" [fragment]="item.fragment ?? null"
[preserveFragment]="item.preserveFragment ?? false" [preserveFragment]="item.preserveFragment ?? false"
@ -18,45 +16,38 @@
[queryParamsHandling]="item.queryParamsHandling ?? null" [queryParamsHandling]="item.queryParamsHandling ?? null"
[routerLinkActive]="'fuse-horizontal-navigation-item-active'" [routerLinkActive]="'fuse-horizontal-navigation-item-active'"
[routerLinkActiveOptions]="isActiveMatchOptions" [routerLinkActiveOptions]="isActiveMatchOptions"
[matTooltip]="item.tooltip || ''" [matTooltip]="item.tooltip || ''">
>
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> <ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
</div> </div>
} </ng-container>
<!-- Item with an external link --> <!-- Item with an external link -->
@if (item.link && item.externalLink && !item.function && !item.disabled) { <ng-container *ngIf="item.link && item.externalLink && !item.function && !item.disabled">
<a <a
class="fuse-horizontal-navigation-item" class="fuse-horizontal-navigation-item"
[href]="item.link" [href]="item.link"
[target]="item.target || '_self'" [target]="item.target || '_self'"
[matTooltip]="item.tooltip || ''" [matTooltip]="item.tooltip || ''">
>
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> <ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
</a> </a>
} </ng-container>
<!-- Item with a function --> <!-- Item with a function -->
@if (!item.link && item.function && !item.disabled) { <ng-container *ngIf="!item.link && item.function && !item.disabled">
<div <div
class="fuse-horizontal-navigation-item" class="fuse-horizontal-navigation-item"
[ngClass]="{ [ngClass]="{'fuse-horizontal-navigation-item-active-forced': item.active}"
'fuse-horizontal-navigation-item-active-forced': item.active,
}"
[matTooltip]="item.tooltip || ''" [matTooltip]="item.tooltip || ''"
(click)="item.function(item)" (click)="item.function(item)">
>
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> <ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
</div> </div>
} </ng-container>
<!-- Item with an internal link and function --> <!-- Item with an internal link and function -->
@if (item.link && !item.externalLink && item.function && !item.disabled) { <ng-container *ngIf="item.link && !item.externalLink && item.function && !item.disabled">
<div <div
class="fuse-horizontal-navigation-item" class="fuse-horizontal-navigation-item"
[ngClass]="{ [ngClass]="{'fuse-horizontal-navigation-item-active-forced': item.active}"
'fuse-horizontal-navigation-item-active-forced': item.active,
}"
[routerLink]="[item.link]" [routerLink]="[item.link]"
[fragment]="item.fragment ?? null" [fragment]="item.fragment ?? null"
[preserveFragment]="item.preserveFragment ?? false" [preserveFragment]="item.preserveFragment ?? false"
@ -65,85 +56,79 @@
[routerLinkActive]="'fuse-horizontal-navigation-item-active'" [routerLinkActive]="'fuse-horizontal-navigation-item-active'"
[routerLinkActiveOptions]="isActiveMatchOptions" [routerLinkActiveOptions]="isActiveMatchOptions"
[matTooltip]="item.tooltip || ''" [matTooltip]="item.tooltip || ''"
(click)="item.function(item)" (click)="item.function(item)">
>
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> <ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
</div> </div>
} </ng-container>
<!-- Item with an external link and function --> <!-- Item with an external link and function -->
@if (item.link && item.externalLink && item.function && !item.disabled) { <ng-container *ngIf="item.link && item.externalLink && item.function && !item.disabled">
<a <a
class="fuse-horizontal-navigation-item" class="fuse-horizontal-navigation-item"
[href]="item.link" [href]="item.link"
[target]="item.target || '_self'" [target]="item.target || '_self'"
[matTooltip]="item.tooltip || ''" [matTooltip]="item.tooltip || ''"
(click)="item.function(item)" (click)="item.function(item)"
mat-menu-item mat-menu-item>
>
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> <ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
</a> </a>
} </ng-container>
<!-- Item with a no link and no function --> <!-- Item with a no link and no function -->
@if (!item.link && !item.function && !item.disabled) { <ng-container *ngIf="!item.link && !item.function && !item.disabled">
<div <div
class="fuse-horizontal-navigation-item" class="fuse-horizontal-navigation-item"
[ngClass]="{ [ngClass]="{'fuse-horizontal-navigation-item-active-forced': item.active}"
'fuse-horizontal-navigation-item-active-forced': item.active, [matTooltip]="item.tooltip || ''">
}"
[matTooltip]="item.tooltip || ''"
>
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> <ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
</div> </div>
} </ng-container>
<!-- Item is disabled --> <!-- Item is disabled -->
@if (item.disabled) { <ng-container *ngIf="item.disabled">
<div <div class="fuse-horizontal-navigation-item fuse-horizontal-navigation-item-disabled">
class="fuse-horizontal-navigation-item fuse-horizontal-navigation-item-disabled"
>
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> <ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
</div> </div>
} </ng-container>
</div> </div>
<!-- Item template --> <!-- Item template -->
<ng-template #itemTemplate> <ng-template #itemTemplate>
<!-- Icon --> <!-- Icon -->
@if (item.icon) { <ng-container *ngIf="item.icon">
<mat-icon <mat-icon
class="fuse-horizontal-navigation-item-icon" class="fuse-horizontal-navigation-item-icon"
[ngClass]="item.classes?.icon" [ngClass]="item.classes?.icon"
[svgIcon]="item.icon" [svgIcon]="item.icon"></mat-icon>
></mat-icon> </ng-container>
}
<!-- Title & Subtitle --> <!-- Title & Subtitle -->
<div class="fuse-horizontal-navigation-item-title-wrapper"> <div class="fuse-horizontal-navigation-item-title-wrapper">
<div class="fuse-horizontal-navigation-item-title"> <div class="fuse-horizontal-navigation-item-title">
<span [ngClass]="item.classes?.title"> <span [ngClass]="item.classes?.title">
{{ item.title }} {{item.title}}
</span> </span>
</div> </div>
@if (item.subtitle) { <ng-container *ngIf="item.subtitle">
<div class="fuse-horizontal-navigation-item-subtitle text-hint"> <div class="fuse-horizontal-navigation-item-subtitle text-hint">
<span [ngClass]="item.classes?.subtitle"> <span [ngClass]="item.classes?.subtitle">
{{ item.subtitle }} {{item.subtitle}}
</span> </span>
</div> </div>
} </ng-container>
</div> </div>
<!-- Badge --> <!-- Badge -->
@if (item.badge) { <ng-container *ngIf="item.badge">
<div class="fuse-horizontal-navigation-item-badge"> <div class="fuse-horizontal-navigation-item-badge">
<div <div
class="fuse-horizontal-navigation-item-badge-content" class="fuse-horizontal-navigation-item-badge-content"
[ngClass]="item.badge.classes" [ngClass]="item.badge.classes">
> {{item.badge.title}}
{{ item.badge.title }}
</div> </div>
</div> </div>
} </ng-container>
</ng-template> </ng-template>

View File

@ -1,21 +1,9 @@
import { NgClass, NgTemplateOutlet } from '@angular/common'; import { NgClass, NgIf, NgTemplateOutlet } from '@angular/common';
import { import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
OnDestroy,
OnInit,
inject,
} from '@angular/core';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { import { IsActiveMatchOptions, RouterLink, RouterLinkActive } from '@angular/router';
IsActiveMatchOptions,
RouterLink,
RouterLinkActive,
} from '@angular/router';
import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component'; import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component';
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
@ -23,39 +11,37 @@ import { FuseUtilsService } from '@fuse/services/utils/utils.service';
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'fuse-horizontal-navigation-basic-item', selector : 'fuse-horizontal-navigation-basic-item',
templateUrl: './basic.component.html', templateUrl : './basic.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ standalone : true,
NgClass, imports : [NgClass, NgIf, RouterLink, RouterLinkActive, MatTooltipModule, NgTemplateOutlet, MatMenuModule, MatIconModule],
RouterLink,
RouterLinkActive,
MatTooltipModule,
NgTemplateOutlet,
MatMenuModule,
MatIconModule,
],
}) })
export class FuseHorizontalNavigationBasicItemComponent export class FuseHorizontalNavigationBasicItemComponent implements OnInit, OnDestroy
implements OnInit, OnDestroy
{ {
private _changeDetectorRef = inject(ChangeDetectorRef);
private _fuseNavigationService = inject(FuseNavigationService);
private _fuseUtilsService = inject(FuseUtilsService);
@Input() item: FuseNavigationItem; @Input() item: FuseNavigationItem;
@Input() name: string; @Input() name: string;
// Set the equivalent of {exact: false} as default for active match options. isActiveMatchOptions: IsActiveMatchOptions;
// We are not assigning the item.isActiveMatchOptions directly to the
// [routerLinkActiveOptions] because if it's "undefined" initially, the router
// will throw an error and stop working.
isActiveMatchOptions: IsActiveMatchOptions =
this._fuseUtilsService.subsetMatchOptions;
private _fuseHorizontalNavigationComponent: FuseHorizontalNavigationComponent; private _fuseHorizontalNavigationComponent: FuseHorizontalNavigationComponent;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _fuseNavigationService: FuseNavigationService,
private _fuseUtilsService: FuseUtilsService,
)
{
// Set the equivalent of {exact: false} as default for active match options.
// We are not assigning the item.isActiveMatchOptions directly to the
// [routerLinkActiveOptions] because if it's "undefined" initially, the router
// will throw an error and stop working.
this.isActiveMatchOptions = this._fuseUtilsService.subsetMatchOptions;
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks // @ Lifecycle hooks
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -63,7 +49,8 @@ export class FuseHorizontalNavigationBasicItemComponent
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Set the "isActiveMatchOptions" either from item's // Set the "isActiveMatchOptions" either from item's
// "isActiveMatchOptions" or the equivalent form of // "isActiveMatchOptions" or the equivalent form of
// item's "exactMatch" option // item's "exactMatch" option
@ -73,25 +60,26 @@ export class FuseHorizontalNavigationBasicItemComponent
: this._fuseUtilsService.subsetMatchOptions; : this._fuseUtilsService.subsetMatchOptions;
// Get the parent navigation component // Get the parent navigation component
this._fuseHorizontalNavigationComponent = this._fuseHorizontalNavigationComponent = this._fuseNavigationService.getComponent(this.name);
this._fuseNavigationService.getComponent(this.name);
// Mark for check // Mark for check
this._changeDetectorRef.markForCheck(); this._changeDetectorRef.markForCheck();
// Subscribe to onRefreshed on the navigation component // Subscribe to onRefreshed on the navigation component
this._fuseHorizontalNavigationComponent.onRefreshed this._fuseHorizontalNavigationComponent.onRefreshed.pipe(
.pipe(takeUntil(this._unsubscribeAll)) takeUntil(this._unsubscribeAll),
.subscribe(() => { ).subscribe(() =>
// Mark for check {
this._changeDetectorRef.markForCheck(); // Mark for check
}); this._changeDetectorRef.markForCheck();
});
} }
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
this._unsubscribeAll.next(null); this._unsubscribeAll.next(null);
this._unsubscribeAll.complete(); this._unsubscribeAll.complete();

View File

@ -1,135 +1,121 @@
@if (!child) { <ng-container *ngIf="!child">
<div <div
[ngClass]="{ [ngClass]="{'fuse-horizontal-navigation-menu-active': trigger.menuOpen,
'fuse-horizontal-navigation-menu-active': trigger.menuOpen, 'fuse-horizontal-navigation-menu-active-forced': item.active}"
'fuse-horizontal-navigation-menu-active-forced': item.active,
}"
[matMenuTriggerFor]="matMenu" [matMenuTriggerFor]="matMenu"
(onMenuOpen)="triggerChangeDetection()" (onMenuOpen)="triggerChangeDetection()"
(onMenuClose)="triggerChangeDetection()" (onMenuClose)="triggerChangeDetection()"
#trigger="matMenuTrigger" #trigger="matMenuTrigger">
> <ng-container *ngTemplateOutlet="itemTemplate; context: {$implicit: item}"></ng-container>
<ng-container
*ngTemplateOutlet="itemTemplate; context: { $implicit: item }"
></ng-container>
</div> </div>
} </ng-container>
<mat-menu <mat-menu
class="fuse-horizontal-navigation-menu-panel" class="fuse-horizontal-navigation-menu-panel"
[overlapTrigger]="false" [overlapTrigger]="false"
#matMenu="matMenu" #matMenu="matMenu">
>
@for (item of item.children; track trackByFn($index, item)) { <ng-container *ngFor="let item of item.children; trackBy: trackByFn">
<!-- Skip the hidden items --> <!-- Skip the hidden items -->
@if ((item.hidden && !item.hidden(item)) || !item.hidden) { <ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden">
<!-- Basic --> <!-- Basic -->
@if (item.type === 'basic') { <ng-container *ngIf="item.type === 'basic'">
<div <div
class="fuse-horizontal-navigation-menu-item" class="fuse-horizontal-navigation-menu-item"
[disabled]="item.disabled" [disabled]="item.disabled"
mat-menu-item mat-menu-item>
>
<fuse-horizontal-navigation-basic-item <fuse-horizontal-navigation-basic-item
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-horizontal-navigation-basic-item>
></fuse-horizontal-navigation-basic-item>
</div> </div>
} </ng-container>
<!-- Branch: aside, collapsable, group --> <!-- Branch: aside, collapsable, group -->
@if ( <ng-container *ngIf="item.type === 'aside' || item.type === 'collapsable' || item.type === 'group'">
item.type === 'aside' ||
item.type === 'collapsable' ||
item.type === 'group'
) {
<div <div
class="fuse-horizontal-navigation-menu-item" class="fuse-horizontal-navigation-menu-item"
[disabled]="item.disabled" [disabled]="item.disabled"
[matMenuTriggerFor]="branch.matMenu" [matMenuTriggerFor]="branch.matMenu"
mat-menu-item mat-menu-item>
> <ng-container *ngTemplateOutlet="itemTemplate; context: {$implicit: item}"></ng-container>
<ng-container
*ngTemplateOutlet="
itemTemplate;
context: { $implicit: item }
"
></ng-container>
<fuse-horizontal-navigation-branch-item <fuse-horizontal-navigation-branch-item
[child]="true" [child]="true"
[item]="item" [item]="item"
[name]="name" [name]="name"
#branch #branch></fuse-horizontal-navigation-branch-item>
></fuse-horizontal-navigation-branch-item>
</div> </div>
} </ng-container>
<!-- Divider --> <!-- Divider -->
@if (item.type === 'divider') { <ng-container *ngIf="item.type === 'divider'">
<div class="fuse-horizontal-navigation-menu-item" mat-menu-item> <div
class="fuse-horizontal-navigation-menu-item"
mat-menu-item>
<fuse-horizontal-navigation-divider-item <fuse-horizontal-navigation-divider-item
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-horizontal-navigation-divider-item>
></fuse-horizontal-navigation-divider-item>
</div> </div>
} </ng-container>
}
} </ng-container>
</ng-container>
</mat-menu> </mat-menu>
<!-- Item template --> <!-- Item template -->
<ng-template let-item #itemTemplate> <ng-template
let-item
#itemTemplate>
<div <div
class="fuse-horizontal-navigation-item-wrapper" class="fuse-horizontal-navigation-item-wrapper"
[class.fuse-horizontal-navigation-item-has-subtitle]="!!item.subtitle" [class.fuse-horizontal-navigation-item-has-subtitle]="!!item.subtitle"
[ngClass]="item.classes?.wrapper" [ngClass]="item.classes?.wrapper">
>
<div <div
class="fuse-horizontal-navigation-item" class="fuse-horizontal-navigation-item"
[ngClass]="{ [ngClass]="{'fuse-horizontal-navigation-item-disabled': item.disabled,
'fuse-horizontal-navigation-item-disabled': item.disabled, 'fuse-horizontal-navigation-item-active-forced': item.active}"
'fuse-horizontal-navigation-item-active-forced': item.active, [matTooltip]="item.tooltip || ''">
}"
[matTooltip]="item.tooltip || ''"
>
<!-- Icon --> <!-- Icon -->
@if (item.icon) { <ng-container *ngIf="item.icon">
<mat-icon <mat-icon
class="fuse-horizontal-navigation-item-icon" class="fuse-horizontal-navigation-item-icon"
[ngClass]="item.classes?.icon" [ngClass]="item.classes?.icon"
[svgIcon]="item.icon" [svgIcon]="item.icon"></mat-icon>
></mat-icon> </ng-container>
}
<!-- Title & Subtitle --> <!-- Title & Subtitle -->
<div class="fuse-horizontal-navigation-item-title-wrapper"> <div class="fuse-horizontal-navigation-item-title-wrapper">
<div class="fuse-horizontal-navigation-item-title"> <div class="fuse-horizontal-navigation-item-title">
<span [ngClass]="item.classes?.title"> <span [ngClass]="item.classes?.title">
{{ item.title }} {{item.title}}
</span> </span>
</div> </div>
@if (item.subtitle) { <ng-container *ngIf="item.subtitle">
<div <div class="fuse-horizontal-navigation-item-subtitle text-hint">
class="fuse-horizontal-navigation-item-subtitle text-hint"
>
<span [ngClass]="item.classes?.subtitle"> <span [ngClass]="item.classes?.subtitle">
{{ item.subtitle }} {{item.subtitle}}
</span> </span>
</div> </div>
} </ng-container>
</div> </div>
<!-- Badge --> <!-- Badge -->
@if (item.badge) { <ng-container *ngIf="item.badge">
<div class="fuse-horizontal-navigation-item-badge"> <div class="fuse-horizontal-navigation-item-badge">
<div <div
class="fuse-horizontal-navigation-item-badge-content" class="fuse-horizontal-navigation-item-badge-content"
[ngClass]="item.badge.classes" [ngClass]="item.badge.classes">
> {{item.badge.title}}
{{ item.badge.title }}
</div> </div>
</div> </div>
} </ng-container>
</div> </div>
</div> </div>
</ng-template> </ng-template>

View File

@ -1,16 +1,6 @@
import { BooleanInput } from '@angular/cdk/coercion'; import { BooleanInput } from '@angular/cdk/coercion';
import { NgClass, NgTemplateOutlet } from '@angular/common'; import { NgClass, NgFor, NgIf, NgTemplateOutlet } from '@angular/common';
import { import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
OnDestroy,
OnInit,
ViewChild,
forwardRef,
inject,
} from '@angular/core';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatMenu, MatMenuModule } from '@angular/material/menu'; import { MatMenu, MatMenuModule } from '@angular/material/menu';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
@ -22,38 +12,36 @@ import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'fuse-horizontal-navigation-branch-item', selector : 'fuse-horizontal-navigation-branch-item',
templateUrl: './branch.component.html', templateUrl : './branch.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ standalone : true,
NgClass, imports : [NgIf, NgClass, MatMenuModule, NgTemplateOutlet, NgFor, FuseHorizontalNavigationBasicItemComponent, forwardRef(() => FuseHorizontalNavigationBranchItemComponent), FuseHorizontalNavigationDividerItemComponent, MatTooltipModule, MatIconModule],
MatMenuModule,
NgTemplateOutlet,
FuseHorizontalNavigationBasicItemComponent,
forwardRef(() => FuseHorizontalNavigationBranchItemComponent),
FuseHorizontalNavigationDividerItemComponent,
MatTooltipModule,
MatIconModule,
],
}) })
export class FuseHorizontalNavigationBranchItemComponent export class FuseHorizontalNavigationBranchItemComponent implements OnInit, OnDestroy
implements OnInit, OnDestroy
{ {
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
static ngAcceptInputType_child: BooleanInput; static ngAcceptInputType_child: BooleanInput;
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
private _changeDetectorRef = inject(ChangeDetectorRef);
private _fuseNavigationService = inject(FuseNavigationService);
@Input() child: boolean = false; @Input() child: boolean = false;
@Input() item: FuseNavigationItem; @Input() item: FuseNavigationItem;
@Input() name: string; @Input() name: string;
@ViewChild('matMenu', { static: true }) matMenu: MatMenu; @ViewChild('matMenu', {static: true}) matMenu: MatMenu;
private _fuseHorizontalNavigationComponent: FuseHorizontalNavigationComponent; private _fuseHorizontalNavigationComponent: FuseHorizontalNavigationComponent;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _fuseNavigationService: FuseNavigationService,
)
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks // @ Lifecycle hooks
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -61,24 +49,26 @@ export class FuseHorizontalNavigationBranchItemComponent
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Get the parent navigation component // Get the parent navigation component
this._fuseHorizontalNavigationComponent = this._fuseHorizontalNavigationComponent = this._fuseNavigationService.getComponent(this.name);
this._fuseNavigationService.getComponent(this.name);
// Subscribe to onRefreshed on the navigation component // Subscribe to onRefreshed on the navigation component
this._fuseHorizontalNavigationComponent.onRefreshed this._fuseHorizontalNavigationComponent.onRefreshed.pipe(
.pipe(takeUntil(this._unsubscribeAll)) takeUntil(this._unsubscribeAll),
.subscribe(() => { ).subscribe(() =>
// Mark for check {
this._changeDetectorRef.markForCheck(); // Mark for check
}); this._changeDetectorRef.markForCheck();
});
} }
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
this._unsubscribeAll.next(null); this._unsubscribeAll.next(null);
this._unsubscribeAll.complete(); this._unsubscribeAll.complete();
@ -91,7 +81,8 @@ export class FuseHorizontalNavigationBranchItemComponent
/** /**
* Trigger the change detection * Trigger the change detection
*/ */
triggerChangeDetection(): void { triggerChangeDetection(): void
{
// Mark for check // Mark for check
this._changeDetectorRef.markForCheck(); this._changeDetectorRef.markForCheck();
} }
@ -102,7 +93,8 @@ export class FuseHorizontalNavigationBranchItemComponent
* @param index * @param index
* @param item * @param item
*/ */
trackByFn(index: number, item: any): any { trackByFn(index: number, item: any): any
{
return item.id || index; return item.id || index;
} }
} }

View File

@ -1,5 +1,4 @@
<!-- Divider --> <!-- Divider -->
<div <div
class="fuse-horizontal-navigation-item-wrapper divider" class="fuse-horizontal-navigation-item-wrapper divider"
[ngClass]="item.classes?.wrapper" [ngClass]="item.classes?.wrapper"></div>
></div>

View File

@ -1,36 +1,35 @@
import { NgClass } from '@angular/common'; import { NgClass } from '@angular/common';
import { import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
inject,
Input,
OnDestroy,
OnInit,
} from '@angular/core';
import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component'; import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component';
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'fuse-horizontal-navigation-divider-item', selector : 'fuse-horizontal-navigation-divider-item',
templateUrl: './divider.component.html', templateUrl : './divider.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgClass], standalone : true,
imports : [NgClass],
}) })
export class FuseHorizontalNavigationDividerItemComponent export class FuseHorizontalNavigationDividerItemComponent implements OnInit, OnDestroy
implements OnInit, OnDestroy
{ {
private _changeDetectorRef = inject(ChangeDetectorRef);
private _fuseNavigationService = inject(FuseNavigationService);
@Input() item: FuseNavigationItem; @Input() item: FuseNavigationItem;
@Input() name: string; @Input() name: string;
private _fuseHorizontalNavigationComponent: FuseHorizontalNavigationComponent; private _fuseHorizontalNavigationComponent: FuseHorizontalNavigationComponent;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _fuseNavigationService: FuseNavigationService,
)
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks // @ Lifecycle hooks
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -38,24 +37,26 @@ export class FuseHorizontalNavigationDividerItemComponent
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Get the parent navigation component // Get the parent navigation component
this._fuseHorizontalNavigationComponent = this._fuseHorizontalNavigationComponent = this._fuseNavigationService.getComponent(this.name);
this._fuseNavigationService.getComponent(this.name);
// Subscribe to onRefreshed on the navigation component // Subscribe to onRefreshed on the navigation component
this._fuseHorizontalNavigationComponent.onRefreshed this._fuseHorizontalNavigationComponent.onRefreshed.pipe(
.pipe(takeUntil(this._unsubscribeAll)) takeUntil(this._unsubscribeAll),
.subscribe(() => { ).subscribe(() =>
// Mark for check {
this._changeDetectorRef.markForCheck(); // Mark for check
}); this._changeDetectorRef.markForCheck();
});
} }
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
this._unsubscribeAll.next(null); this._unsubscribeAll.next(null);
this._unsubscribeAll.complete(); this._unsubscribeAll.complete();

View File

@ -1,5 +1,4 @@
<!-- Spacer --> <!-- Spacer -->
<div <div
class="fuse-horizontal-navigation-item-wrapper" class="fuse-horizontal-navigation-item-wrapper"
[ngClass]="item.classes?.wrapper" [ngClass]="item.classes?.wrapper"></div>
></div>

View File

@ -1,36 +1,35 @@
import { NgClass } from '@angular/common'; import { NgClass } from '@angular/common';
import { import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
inject,
Input,
OnDestroy,
OnInit,
} from '@angular/core';
import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component'; import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component';
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'fuse-horizontal-navigation-spacer-item', selector : 'fuse-horizontal-navigation-spacer-item',
templateUrl: './spacer.component.html', templateUrl : './spacer.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgClass], standalone : true,
imports : [NgClass],
}) })
export class FuseHorizontalNavigationSpacerItemComponent export class FuseHorizontalNavigationSpacerItemComponent implements OnInit, OnDestroy
implements OnInit, OnDestroy
{ {
private _changeDetectorRef = inject(ChangeDetectorRef);
private _fuseNavigationService = inject(FuseNavigationService);
@Input() item: FuseNavigationItem; @Input() item: FuseNavigationItem;
@Input() name: string; @Input() name: string;
private _fuseHorizontalNavigationComponent: FuseHorizontalNavigationComponent; private _fuseHorizontalNavigationComponent: FuseHorizontalNavigationComponent;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _fuseNavigationService: FuseNavigationService,
)
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks // @ Lifecycle hooks
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -38,24 +37,26 @@ export class FuseHorizontalNavigationSpacerItemComponent
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Get the parent navigation component // Get the parent navigation component
this._fuseHorizontalNavigationComponent = this._fuseHorizontalNavigationComponent = this._fuseNavigationService.getComponent(this.name);
this._fuseNavigationService.getComponent(this.name);
// Subscribe to onRefreshed on the navigation component // Subscribe to onRefreshed on the navigation component
this._fuseHorizontalNavigationComponent.onRefreshed this._fuseHorizontalNavigationComponent.onRefreshed.pipe(
.pipe(takeUntil(this._unsubscribeAll)) takeUntil(this._unsubscribeAll),
.subscribe(() => { ).subscribe(() =>
// Mark for check {
this._changeDetectorRef.markForCheck(); // Mark for check
}); this._changeDetectorRef.markForCheck();
});
} }
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
this._unsubscribeAll.next(null); this._unsubscribeAll.next(null);
this._unsubscribeAll.complete(); this._unsubscribeAll.complete();

View File

@ -1,37 +1,36 @@
<div class="fuse-horizontal-navigation-wrapper"> <div class="fuse-horizontal-navigation-wrapper">
@for (item of navigation; track trackByFn($index, item)) {
<ng-container *ngFor="let item of navigation; trackBy: trackByFn">
<!-- Skip the hidden items --> <!-- Skip the hidden items -->
@if ((item.hidden && !item.hidden(item)) || !item.hidden) { <ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden">
<!-- Basic --> <!-- Basic -->
@if (item.type === 'basic') { <ng-container *ngIf="item.type === 'basic'">
<fuse-horizontal-navigation-basic-item <fuse-horizontal-navigation-basic-item
class="fuse-horizontal-navigation-menu-item" class="fuse-horizontal-navigation-menu-item"
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-horizontal-navigation-basic-item>
></fuse-horizontal-navigation-basic-item> </ng-container>
}
<!-- Branch: aside, collapsable, group --> <!-- Branch: aside, collapsable, group -->
@if ( <ng-container *ngIf="item.type === 'aside' || item.type === 'collapsable' || item.type === 'group'">
item.type === 'aside' ||
item.type === 'collapsable' ||
item.type === 'group'
) {
<fuse-horizontal-navigation-branch-item <fuse-horizontal-navigation-branch-item
class="fuse-horizontal-navigation-menu-item" class="fuse-horizontal-navigation-menu-item"
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-horizontal-navigation-branch-item>
></fuse-horizontal-navigation-branch-item> </ng-container>
}
<!-- Spacer --> <!-- Spacer -->
@if (item.type === 'spacer') { <ng-container *ngIf="item.type === 'spacer'">
<fuse-horizontal-navigation-spacer-item <fuse-horizontal-navigation-spacer-item
class="fuse-horizontal-navigation-menu-item" class="fuse-horizontal-navigation-menu-item"
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-horizontal-navigation-spacer-item>
></fuse-horizontal-navigation-spacer-item> </ng-container>
}
} </ng-container>
}
</ng-container>
</div> </div>

View File

@ -1,5 +1,6 @@
/* Root navigation specific */ /* Root navigation specific */
fuse-horizontal-navigation { fuse-horizontal-navigation {
.fuse-horizontal-navigation-wrapper { .fuse-horizontal-navigation-wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
@ -7,8 +8,11 @@ fuse-horizontal-navigation {
/* Basic, Branch */ /* Basic, Branch */
fuse-horizontal-navigation-basic-item, fuse-horizontal-navigation-basic-item,
fuse-horizontal-navigation-branch-item { fuse-horizontal-navigation-branch-item {
@screen sm { @screen sm {
&:hover { &:hover {
.fuse-horizontal-navigation-item-wrapper { .fuse-horizontal-navigation-item-wrapper {
@apply bg-hover; @apply bg-hover;
} }
@ -33,8 +37,10 @@ fuse-horizontal-navigation {
/* Basic - When item active (current link) */ /* Basic - When item active (current link) */
fuse-horizontal-navigation-basic-item { fuse-horizontal-navigation-basic-item {
.fuse-horizontal-navigation-item-active, .fuse-horizontal-navigation-item-active,
.fuse-horizontal-navigation-item-active-forced { .fuse-horizontal-navigation-item-active-forced {
.fuse-horizontal-navigation-item-title { .fuse-horizontal-navigation-item-title {
@apply text-primary #{'!important'}; @apply text-primary #{'!important'};
} }
@ -55,8 +61,10 @@ fuse-horizontal-navigation {
/* Branch - When menu open */ /* Branch - When menu open */
fuse-horizontal-navigation-branch-item { fuse-horizontal-navigation-branch-item {
.fuse-horizontal-navigation-menu-active, .fuse-horizontal-navigation-menu-active,
.fuse-horizontal-navigation-menu-active-forced { .fuse-horizontal-navigation-menu-active-forced {
.fuse-horizontal-navigation-item-wrapper { .fuse-horizontal-navigation-item-wrapper {
@apply bg-hover; @apply bg-hover;
} }
@ -72,6 +80,7 @@ fuse-horizontal-navigation {
/* Menu panel specific */ /* Menu panel specific */
.fuse-horizontal-navigation-menu-panel { .fuse-horizontal-navigation-menu-panel {
.fuse-horizontal-navigation-menu-item { .fuse-horizontal-navigation-menu-item {
height: auto; height: auto;
min-height: 0; min-height: 0;
@ -100,10 +109,13 @@ fuse-horizontal-navigation {
/* Navigation menu item common */ /* Navigation menu item common */
.fuse-horizontal-navigation-menu-item { .fuse-horizontal-navigation-menu-item {
/* Basic - When item active (current link) */ /* Basic - When item active (current link) */
fuse-horizontal-navigation-basic-item { fuse-horizontal-navigation-basic-item {
.fuse-horizontal-navigation-item-active, .fuse-horizontal-navigation-item-active,
.fuse-horizontal-navigation-item-active-forced { .fuse-horizontal-navigation-item-active-forced {
.fuse-horizontal-navigation-item-title { .fuse-horizontal-navigation-item-title {
@apply text-primary #{'!important'}; @apply text-primary #{'!important'};
} }
@ -126,6 +138,7 @@ fuse-horizontal-navigation {
width: 100%; width: 100%;
&.fuse-horizontal-navigation-item-has-subtitle { &.fuse-horizontal-navigation-item-has-subtitle {
.fuse-horizontal-navigation-item { .fuse-horizontal-navigation-item {
min-height: 56px; min-height: 56px;
} }
@ -143,6 +156,7 @@ fuse-horizontal-navigation {
text-decoration: none; text-decoration: none;
.fuse-horizontal-navigation-item-title-wrapper { .fuse-horizontal-navigation-item-title-wrapper {
.fuse-horizontal-navigation-item-subtitle { .fuse-horizontal-navigation-item-subtitle {
font-size: 12px; font-size: 12px;
} }

View File

@ -1,15 +1,5 @@
import { import { NgFor, NgIf } from '@angular/common';
ChangeDetectionStrategy, import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core';
ChangeDetectorRef,
Component,
Input,
OnChanges,
OnDestroy,
OnInit,
SimpleChanges,
ViewEncapsulation,
inject,
} from '@angular/core';
import { fuseAnimations } from '@fuse/animations'; import { fuseAnimations } from '@fuse/animations';
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
@ -20,32 +10,35 @@ import { FuseHorizontalNavigationBranchItemComponent } from './components/branch
import { FuseHorizontalNavigationSpacerItemComponent } from './components/spacer/spacer.component'; import { FuseHorizontalNavigationSpacerItemComponent } from './components/spacer/spacer.component';
@Component({ @Component({
selector: 'fuse-horizontal-navigation', selector : 'fuse-horizontal-navigation',
templateUrl: './horizontal.component.html', templateUrl : './horizontal.component.html',
styleUrls: ['./horizontal.component.scss'], styleUrls : ['./horizontal.component.scss'],
animations: fuseAnimations, animations : fuseAnimations,
encapsulation: ViewEncapsulation.None, encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'fuseHorizontalNavigation', exportAs : 'fuseHorizontalNavigation',
imports: [ standalone : true,
FuseHorizontalNavigationBasicItemComponent, imports : [NgFor, NgIf, FuseHorizontalNavigationBasicItemComponent, FuseHorizontalNavigationBranchItemComponent, FuseHorizontalNavigationSpacerItemComponent],
FuseHorizontalNavigationBranchItemComponent,
FuseHorizontalNavigationSpacerItemComponent,
],
}) })
export class FuseHorizontalNavigationComponent export class FuseHorizontalNavigationComponent implements OnChanges, OnInit, OnDestroy
implements OnChanges, OnInit, OnDestroy
{ {
private _changeDetectorRef = inject(ChangeDetectorRef);
private _fuseNavigationService = inject(FuseNavigationService);
private _fuseUtilsService = inject(FuseUtilsService);
@Input() name: string = this._fuseUtilsService.randomId(); @Input() name: string = this._fuseUtilsService.randomId();
@Input() navigation: FuseNavigationItem[]; @Input() navigation: FuseNavigationItem[];
onRefreshed: ReplaySubject<boolean> = new ReplaySubject<boolean>(1); onRefreshed: ReplaySubject<boolean> = new ReplaySubject<boolean>(1);
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _fuseNavigationService: FuseNavigationService,
private _fuseUtilsService: FuseUtilsService,
)
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks // @ Lifecycle hooks
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -55,9 +48,11 @@ export class FuseHorizontalNavigationComponent
* *
* @param changes * @param changes
*/ */
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void
{
// Navigation // Navigation
if ('navigation' in changes) { if ( 'navigation' in changes )
{
// Mark for check // Mark for check
this._changeDetectorRef.markForCheck(); this._changeDetectorRef.markForCheck();
} }
@ -66,9 +61,11 @@ export class FuseHorizontalNavigationComponent
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Make sure the name input is not an empty string // Make sure the name input is not an empty string
if (this.name === '') { if ( this.name === '' )
{
this.name = this._fuseUtilsService.randomId(); this.name = this._fuseUtilsService.randomId();
} }
@ -79,7 +76,8 @@ export class FuseHorizontalNavigationComponent
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
// Deregister the navigation component from the registry // Deregister the navigation component from the registry
this._fuseNavigationService.deregisterComponent(this.name); this._fuseNavigationService.deregisterComponent(this.name);
@ -95,7 +93,8 @@ export class FuseHorizontalNavigationComponent
/** /**
* Refresh the component to apply the changes * Refresh the component to apply the changes
*/ */
refresh(): void { refresh(): void
{
// Mark for check // Mark for check
this._changeDetectorRef.markForCheck(); this._changeDetectorRef.markForCheck();
@ -109,7 +108,8 @@ export class FuseHorizontalNavigationComponent
* @param index * @param index
* @param item * @param item
*/ */
trackByFn(index: number, item: any): any { trackByFn(index: number, item: any): any
{
return item.id || index; return item.id || index;
} }
} }

View File

@ -1,13 +1,18 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
@Injectable({ providedIn: 'root' }) @Injectable({providedIn: 'root'})
export class FuseNavigationService { export class FuseNavigationService
{
private _componentRegistry: Map<string, any> = new Map<string, any>(); private _componentRegistry: Map<string, any> = new Map<string, any>();
private _navigationStore: Map<string, FuseNavigationItem[]> = new Map< private _navigationStore: Map<string, FuseNavigationItem[]> = new Map<string, any>();
string,
any /**
>(); * Constructor
*/
constructor()
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Public methods // @ Public methods
@ -19,7 +24,8 @@ export class FuseNavigationService {
* @param name * @param name
* @param component * @param component
*/ */
registerComponent(name: string, component: any): void { registerComponent(name: string, component: any): void
{
this._componentRegistry.set(name, component); this._componentRegistry.set(name, component);
} }
@ -28,7 +34,8 @@ export class FuseNavigationService {
* *
* @param name * @param name
*/ */
deregisterComponent(name: string): void { deregisterComponent(name: string): void
{
this._componentRegistry.delete(name); this._componentRegistry.delete(name);
} }
@ -37,7 +44,8 @@ export class FuseNavigationService {
* *
* @param name * @param name
*/ */
getComponent<T>(name: string): T { getComponent<T>(name: string): T
{
return this._componentRegistry.get(name); return this._componentRegistry.get(name);
} }
@ -47,7 +55,8 @@ export class FuseNavigationService {
* @param key * @param key
* @param navigation * @param navigation
*/ */
storeNavigation(key: string, navigation: FuseNavigationItem[]): void { storeNavigation(key: string, navigation: FuseNavigationItem[]): void
{
// Add to the store // Add to the store
this._navigationStore.set(key, navigation); this._navigationStore.set(key, navigation);
} }
@ -57,7 +66,8 @@ export class FuseNavigationService {
* *
* @param key * @param key
*/ */
getNavigation(key: string): FuseNavigationItem[] { getNavigation(key: string): FuseNavigationItem[]
{
return this._navigationStore.get(key) ?? []; return this._navigationStore.get(key) ?? [];
} }
@ -66,12 +76,12 @@ export class FuseNavigationService {
* *
* @param key * @param key
*/ */
deleteNavigation(key: string): void { deleteNavigation(key: string): void
{
// Check if the navigation exists // Check if the navigation exists
if (!this._navigationStore.has(key)) { if ( !this._navigationStore.has(key) )
console.warn( {
`Navigation with the key '${key}' does not exist in the store.` console.warn(`Navigation with the key '${key}' does not exist in the store.`);
);
} }
// Delete from the storage // Delete from the storage
@ -85,22 +95,20 @@ export class FuseNavigationService {
* @param navigation * @param navigation
* @param flatNavigation * @param flatNavigation
*/ */
getFlatNavigation( getFlatNavigation(navigation: FuseNavigationItem[], flatNavigation: FuseNavigationItem[] = []): FuseNavigationItem[]
navigation: FuseNavigationItem[], {
flatNavigation: FuseNavigationItem[] = [] for ( const item of navigation )
): FuseNavigationItem[] { {
for (const item of navigation) { if ( item.type === 'basic' )
if (item.type === 'basic') { {
flatNavigation.push(item); flatNavigation.push(item);
continue; continue;
} }
if ( if ( item.type === 'aside' || item.type === 'collapsable' || item.type === 'group' )
item.type === 'aside' || {
item.type === 'collapsable' || if ( item.children )
item.type === 'group' {
) {
if (item.children) {
this.getFlatNavigation(item.children, flatNavigation); this.getFlatNavigation(item.children, flatNavigation);
} }
} }
@ -116,19 +124,21 @@ export class FuseNavigationService {
* @param id * @param id
* @param navigation * @param navigation
*/ */
getItem( getItem(id: string, navigation: FuseNavigationItem[]): FuseNavigationItem | null
id: string, {
navigation: FuseNavigationItem[] for ( const item of navigation )
): FuseNavigationItem | null { {
for (const item of navigation) { if ( item.id === id )
if (item.id === id) { {
return item; return item;
} }
if (item.children) { if ( item.children )
{
const childItem = this.getItem(id, item.children); const childItem = this.getItem(id, item.children);
if (childItem) { if ( childItem )
{
return childItem; return childItem;
} }
} }
@ -148,17 +158,22 @@ export class FuseNavigationService {
getItemParent( getItemParent(
id: string, id: string,
navigation: FuseNavigationItem[], navigation: FuseNavigationItem[],
parent: FuseNavigationItem[] | FuseNavigationItem parent: FuseNavigationItem[] | FuseNavigationItem,
): FuseNavigationItem[] | FuseNavigationItem | null { ): FuseNavigationItem[] | FuseNavigationItem | null
for (const item of navigation) { {
if (item.id === id) { for ( const item of navigation )
{
if ( item.id === id )
{
return parent; return parent;
} }
if (item.children) { if ( item.children )
{
const childItem = this.getItemParent(id, item.children, item); const childItem = this.getItemParent(id, item.children, item);
if (childItem) { if ( childItem )
{
return childItem; return childItem;
} }
} }

View File

@ -1,14 +1,17 @@
import { import { IsActiveMatchOptions, Params, QueryParamsHandling } from '@angular/router';
IsActiveMatchOptions,
Params,
QueryParamsHandling,
} from '@angular/router';
export interface FuseNavigationItem { export interface FuseNavigationItem
{
id?: string; id?: string;
title?: string; title?: string;
subtitle?: string; subtitle?: string;
type: 'aside' | 'basic' | 'collapsable' | 'divider' | 'group' | 'spacer'; type:
| 'aside'
| 'basic'
| 'collapsable'
| 'divider'
| 'group'
| 'spacer';
hidden?: (item: FuseNavigationItem) => boolean; hidden?: (item: FuseNavigationItem) => boolean;
active?: boolean; active?: boolean;
disabled?: boolean; disabled?: boolean;
@ -19,7 +22,12 @@ export interface FuseNavigationItem {
queryParams?: Params | null; queryParams?: Params | null;
queryParamsHandling?: QueryParamsHandling | null; queryParamsHandling?: QueryParamsHandling | null;
externalLink?: boolean; externalLink?: boolean;
target?: '_blank' | '_self' | '_parent' | '_top' | string; target?:
| '_blank'
| '_self'
| '_parent'
| '_top'
| string;
exactMatch?: boolean; exactMatch?: boolean;
isActiveMatchOptions?: IsActiveMatchOptions; isActiveMatchOptions?: IsActiveMatchOptions;
function?: (item: FuseNavigationItem) => void; function?: (item: FuseNavigationItem) => void;
@ -44,6 +52,10 @@ export type FuseVerticalNavigationAppearance =
| 'dense' | 'dense'
| 'thin'; | 'thin';
export type FuseVerticalNavigationMode = 'over' | 'side'; export type FuseVerticalNavigationMode =
| 'over'
| 'side';
export type FuseVerticalNavigationPosition = 'left' | 'right'; export type FuseVerticalNavigationPosition =
| 'left'
| 'right';

View File

@ -1,4 +1,4 @@
export * from '@fuse/components/navigation/horizontal/horizontal.component'; export * from '@fuse/components/navigation/horizontal/horizontal.component';
export * from '@fuse/components/navigation/vertical/vertical.component';
export * from '@fuse/components/navigation/navigation.service'; export * from '@fuse/components/navigation/navigation.service';
export * from '@fuse/components/navigation/navigation.types'; export * from '@fuse/components/navigation/navigation.types';
export * from '@fuse/components/navigation/vertical/vertical.component';

View File

@ -1,102 +1,103 @@
<div <div
class="fuse-vertical-navigation-item-wrapper" class="fuse-vertical-navigation-item-wrapper"
[class.fuse-vertical-navigation-item-has-subtitle]="!!item.subtitle" [class.fuse-vertical-navigation-item-has-subtitle]="!!item.subtitle"
[ngClass]="item.classes?.wrapper" [ngClass]="item.classes?.wrapper">
>
<div <div
class="fuse-vertical-navigation-item" class="fuse-vertical-navigation-item"
[ngClass]="{ [ngClass]="{'fuse-vertical-navigation-item-active': active,
'fuse-vertical-navigation-item-active': active, 'fuse-vertical-navigation-item-disabled': item.disabled,
'fuse-vertical-navigation-item-disabled': item.disabled, 'fuse-vertical-navigation-item-active-forced': item.active}"
'fuse-vertical-navigation-item-active-forced': item.active, [matTooltip]="item.tooltip || ''">
}"
[matTooltip]="item.tooltip || ''"
>
<!-- Icon --> <!-- Icon -->
@if (item.icon) { <ng-container *ngIf="item.icon">
<mat-icon <mat-icon
class="fuse-vertical-navigation-item-icon" class="fuse-vertical-navigation-item-icon"
[ngClass]="item.classes?.icon" [ngClass]="item.classes?.icon"
[svgIcon]="item.icon" [svgIcon]="item.icon"></mat-icon>
></mat-icon> </ng-container>
}
<!-- Title & Subtitle --> <!-- Title & Subtitle -->
<div class="fuse-vertical-navigation-item-title-wrapper"> <div class="fuse-vertical-navigation-item-title-wrapper">
<div class="fuse-vertical-navigation-item-title"> <div class="fuse-vertical-navigation-item-title">
<span [ngClass]="item.classes?.title"> <span [ngClass]="item.classes?.title">
{{ item.title }} {{item.title}}
</span> </span>
</div> </div>
@if (item.subtitle) { <ng-container *ngIf="item.subtitle">
<div class="fuse-vertical-navigation-item-subtitle"> <div class="fuse-vertical-navigation-item-subtitle">
<span [ngClass]="item.classes?.subtitle"> <span [ngClass]="item.classes?.subtitle">
{{ item.subtitle }} {{item.subtitle}}
</span> </span>
</div> </div>
} </ng-container>
</div> </div>
<!-- Badge --> <!-- Badge -->
@if (item.badge) { <ng-container *ngIf="item.badge">
<div class="fuse-vertical-navigation-item-badge"> <div class="fuse-vertical-navigation-item-badge">
<div <div
class="fuse-vertical-navigation-item-badge-content" class="fuse-vertical-navigation-item-badge-content"
[ngClass]="item.badge.classes" [ngClass]="item.badge.classes">
> {{item.badge.title}}
{{ item.badge.title }}
</div> </div>
</div> </div>
} </ng-container>
</div> </div>
</div> </div>
@if (!skipChildren) { <ng-container *ngIf="!skipChildren">
<div class="fuse-vertical-navigation-item-children"> <div class="fuse-vertical-navigation-item-children">
@for (item of item.children; track trackByFn($index, item)) {
<ng-container *ngFor="let item of item.children; trackBy: trackByFn">
<!-- Skip the hidden items --> <!-- Skip the hidden items -->
@if ((item.hidden && !item.hidden(item)) || !item.hidden) { <ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden">
<!-- Basic --> <!-- Basic -->
@if (item.type === 'basic') { <ng-container *ngIf="item.type === 'basic'">
<fuse-vertical-navigation-basic-item <fuse-vertical-navigation-basic-item
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-vertical-navigation-basic-item>
></fuse-vertical-navigation-basic-item> </ng-container>
}
<!-- Collapsable --> <!-- Collapsable -->
@if (item.type === 'collapsable') { <ng-container *ngIf="item.type === 'collapsable'">
<fuse-vertical-navigation-collapsable-item <fuse-vertical-navigation-collapsable-item
[item]="item" [item]="item"
[name]="name" [name]="name"
[autoCollapse]="autoCollapse" [autoCollapse]="autoCollapse"></fuse-vertical-navigation-collapsable-item>
></fuse-vertical-navigation-collapsable-item> </ng-container>
}
<!-- Divider --> <!-- Divider -->
@if (item.type === 'divider') { <ng-container *ngIf="item.type === 'divider'">
<fuse-vertical-navigation-divider-item <fuse-vertical-navigation-divider-item
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-vertical-navigation-divider-item>
></fuse-vertical-navigation-divider-item> </ng-container>
}
<!-- Group --> <!-- Group -->
@if (item.type === 'group') { <ng-container *ngIf="item.type === 'group'">
<fuse-vertical-navigation-group-item <fuse-vertical-navigation-group-item
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-vertical-navigation-group-item>
></fuse-vertical-navigation-group-item> </ng-container>
}
<!-- Spacer --> <!-- Spacer -->
@if (item.type === 'spacer') { <ng-container *ngIf="item.type === 'spacer'">
<fuse-vertical-navigation-spacer-item <fuse-vertical-navigation-spacer-item
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-vertical-navigation-spacer-item>
></fuse-vertical-navigation-spacer-item> </ng-container>
}
} </ng-container>
}
</ng-container>
</div> </div>
}
</ng-container>

View File

@ -1,16 +1,6 @@
import { BooleanInput } from '@angular/cdk/coercion'; import { BooleanInput } from '@angular/cdk/coercion';
import { NgClass } from '@angular/common'; import { NgClass, NgFor, NgIf } from '@angular/common';
import { import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
OnChanges,
OnDestroy,
OnInit,
SimpleChanges,
inject,
} from '@angular/core';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { NavigationEnd, Router } from '@angular/router'; import { NavigationEnd, Router } from '@angular/router';
@ -22,35 +12,22 @@ import { FuseVerticalNavigationDividerItemComponent } from '@fuse/components/nav
import { FuseVerticalNavigationGroupItemComponent } from '@fuse/components/navigation/vertical/components/group/group.component'; import { FuseVerticalNavigationGroupItemComponent } from '@fuse/components/navigation/vertical/components/group/group.component';
import { FuseVerticalNavigationSpacerItemComponent } from '@fuse/components/navigation/vertical/components/spacer/spacer.component'; import { FuseVerticalNavigationSpacerItemComponent } from '@fuse/components/navigation/vertical/components/spacer/spacer.component';
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component'; import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component';
import { Subject, filter, takeUntil } from 'rxjs'; import { filter, Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'fuse-vertical-navigation-aside-item', selector : 'fuse-vertical-navigation-aside-item',
templateUrl: './aside.component.html', templateUrl : './aside.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ standalone : true,
NgClass, imports : [NgClass, MatTooltipModule, NgIf, MatIconModule, NgFor, FuseVerticalNavigationBasicItemComponent, FuseVerticalNavigationCollapsableItemComponent, FuseVerticalNavigationDividerItemComponent, FuseVerticalNavigationGroupItemComponent, FuseVerticalNavigationSpacerItemComponent],
MatTooltipModule,
MatIconModule,
FuseVerticalNavigationBasicItemComponent,
FuseVerticalNavigationCollapsableItemComponent,
FuseVerticalNavigationDividerItemComponent,
FuseVerticalNavigationGroupItemComponent,
FuseVerticalNavigationSpacerItemComponent,
],
}) })
export class FuseVerticalNavigationAsideItemComponent export class FuseVerticalNavigationAsideItemComponent implements OnChanges, OnInit, OnDestroy
implements OnChanges, OnInit, OnDestroy
{ {
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
static ngAcceptInputType_autoCollapse: BooleanInput; static ngAcceptInputType_autoCollapse: BooleanInput;
static ngAcceptInputType_skipChildren: BooleanInput; static ngAcceptInputType_skipChildren: BooleanInput;
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
private _changeDetectorRef = inject(ChangeDetectorRef);
private _router = inject(Router);
private _fuseNavigationService = inject(FuseNavigationService);
@Input() activeItemId: string; @Input() activeItemId: string;
@Input() autoCollapse: boolean; @Input() autoCollapse: boolean;
@Input() item: FuseNavigationItem; @Input() item: FuseNavigationItem;
@ -61,6 +38,17 @@ export class FuseVerticalNavigationAsideItemComponent
private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent; private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _router: Router,
private _fuseNavigationService: FuseNavigationService,
)
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks // @ Lifecycle hooks
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -70,9 +58,11 @@ export class FuseVerticalNavigationAsideItemComponent
* *
* @param changes * @param changes
*/ */
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void
{
// Active item id // Active item id
if ('activeItemId' in changes) { if ( 'activeItemId' in changes )
{
// Mark if active // Mark if active
this._markIfActive(this._router.url); this._markIfActive(this._router.url);
} }
@ -81,41 +71,41 @@ export class FuseVerticalNavigationAsideItemComponent
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Mark if active // Mark if active
this._markIfActive(this._router.url); this._markIfActive(this._router.url);
// Attach a listener to the NavigationEnd event // Attach a listener to the NavigationEnd event
this._router.events this._router.events
.pipe( .pipe(
filter( filter((event): event is NavigationEnd => event instanceof NavigationEnd),
(event): event is NavigationEnd => takeUntil(this._unsubscribeAll),
event instanceof NavigationEnd
),
takeUntil(this._unsubscribeAll)
) )
.subscribe((event: NavigationEnd) => { .subscribe((event: NavigationEnd) =>
{
// Mark if active // Mark if active
this._markIfActive(event.urlAfterRedirects); this._markIfActive(event.urlAfterRedirects);
}); });
// Get the parent navigation component // Get the parent navigation component
this._fuseVerticalNavigationComponent = this._fuseVerticalNavigationComponent = this._fuseNavigationService.getComponent(this.name);
this._fuseNavigationService.getComponent(this.name);
// Subscribe to onRefreshed on the navigation component // Subscribe to onRefreshed on the navigation component
this._fuseVerticalNavigationComponent.onRefreshed this._fuseVerticalNavigationComponent.onRefreshed.pipe(
.pipe(takeUntil(this._unsubscribeAll)) takeUntil(this._unsubscribeAll),
.subscribe(() => { ).subscribe(() =>
// Mark for check {
this._changeDetectorRef.markForCheck(); // Mark for check
}); this._changeDetectorRef.markForCheck();
});
} }
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
this._unsubscribeAll.next(null); this._unsubscribeAll.next(null);
this._unsubscribeAll.complete(); this._unsubscribeAll.complete();
@ -131,7 +121,8 @@ export class FuseVerticalNavigationAsideItemComponent
* @param index * @param index
* @param item * @param item
*/ */
trackByFn(index: number, item: any): any { trackByFn(index: number, item: any): any
{
return item.id || index; return item.id || index;
} }
@ -147,33 +138,34 @@ export class FuseVerticalNavigationAsideItemComponent
* @param currentUrl * @param currentUrl
* @private * @private
*/ */
private _hasActiveChild( private _hasActiveChild(item: FuseNavigationItem, currentUrl: string): boolean
item: FuseNavigationItem, {
currentUrl: string
): boolean {
const children = item.children; const children = item.children;
if (!children) { if ( !children )
{
return false; return false;
} }
for (const child of children) { for ( const child of children )
if (child.children) { {
if (this._hasActiveChild(child, currentUrl)) { if ( child.children )
{
if ( this._hasActiveChild(child, currentUrl) )
{
return true; return true;
} }
} }
// Skip items other than 'basic' // Skip items other than 'basic'
if (child.type !== 'basic') { if ( child.type !== 'basic' )
{
continue; continue;
} }
// Check if the child has a link and is active // Check if the child has a link and is active
if ( if ( child.link && this._router.isActive(child.link, child.exactMatch || false) )
child.link && {
this._router.isActive(child.link, child.exactMatch || false)
) {
return true; return true;
} }
} }
@ -186,13 +178,15 @@ export class FuseVerticalNavigationAsideItemComponent
* *
* @private * @private
*/ */
private _markIfActive(currentUrl: string): void { private _markIfActive(currentUrl: string): void
{
// Check if the activeItemId is equals to this item id // Check if the activeItemId is equals to this item id
this.active = this.activeItemId === this.item.id; this.active = this.activeItemId === this.item.id;
// If the aside has a children that is active, // If the aside has a children that is active,
// always mark it as active // always mark it as active
if (this._hasActiveChild(this.item, currentUrl)) { if ( this._hasActiveChild(this.item, currentUrl) )
{
this.active = true; this.active = true;
} }

View File

@ -2,15 +2,13 @@
<div <div
class="fuse-vertical-navigation-item-wrapper" class="fuse-vertical-navigation-item-wrapper"
[class.fuse-vertical-navigation-item-has-subtitle]="!!item.subtitle" [class.fuse-vertical-navigation-item-has-subtitle]="!!item.subtitle"
[ngClass]="item.classes?.wrapper" [ngClass]="item.classes?.wrapper">
>
<!-- Item with an internal link --> <!-- Item with an internal link -->
@if (item.link && !item.externalLink && !item.function && !item.disabled) { <ng-container *ngIf="item.link && !item.externalLink && !item.function && !item.disabled">
<a <a
class="fuse-vertical-navigation-item" class="fuse-vertical-navigation-item"
[ngClass]="{ [ngClass]="{'fuse-vertical-navigation-item-active-forced': item.active}"
'fuse-vertical-navigation-item-active-forced': item.active,
}"
[routerLink]="[item.link]" [routerLink]="[item.link]"
[fragment]="item.fragment ?? null" [fragment]="item.fragment ?? null"
[preserveFragment]="item.preserveFragment ?? false" [preserveFragment]="item.preserveFragment ?? false"
@ -18,45 +16,38 @@
[queryParamsHandling]="item.queryParamsHandling ?? null" [queryParamsHandling]="item.queryParamsHandling ?? null"
[routerLinkActive]="'fuse-vertical-navigation-item-active'" [routerLinkActive]="'fuse-vertical-navigation-item-active'"
[routerLinkActiveOptions]="isActiveMatchOptions" [routerLinkActiveOptions]="isActiveMatchOptions"
[matTooltip]="item.tooltip || ''" [matTooltip]="item.tooltip || ''">
>
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> <ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
</a> </a>
} </ng-container>
<!-- Item with an external link --> <!-- Item with an external link -->
@if (item.link && item.externalLink && !item.function && !item.disabled) { <ng-container *ngIf="item.link && item.externalLink && !item.function && !item.disabled">
<a <a
class="fuse-vertical-navigation-item" class="fuse-vertical-navigation-item"
[href]="item.link" [href]="item.link"
[target]="item.target || '_self'" [target]="item.target || '_self'"
[matTooltip]="item.tooltip || ''" [matTooltip]="item.tooltip || ''">
>
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> <ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
</a> </a>
} </ng-container>
<!-- Item with a function --> <!-- Item with a function -->
@if (!item.link && item.function && !item.disabled) { <ng-container *ngIf="!item.link && item.function && !item.disabled">
<div <div
class="fuse-vertical-navigation-item" class="fuse-vertical-navigation-item"
[ngClass]="{ [ngClass]="{'fuse-vertical-navigation-item-active-forced': item.active}"
'fuse-vertical-navigation-item-active-forced': item.active,
}"
[matTooltip]="item.tooltip || ''" [matTooltip]="item.tooltip || ''"
(click)="item.function(item)" (click)="item.function(item)">
>
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> <ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
</div> </div>
} </ng-container>
<!-- Item with an internal link and function --> <!-- Item with an internal link and function -->
@if (item.link && !item.externalLink && item.function && !item.disabled) { <ng-container *ngIf="item.link && !item.externalLink && item.function && !item.disabled">
<a <a
class="fuse-vertical-navigation-item" class="fuse-vertical-navigation-item"
[ngClass]="{ [ngClass]="{'fuse-vertical-navigation-item-active-forced': item.active}"
'fuse-vertical-navigation-item-active-forced': item.active,
}"
[routerLink]="[item.link]" [routerLink]="[item.link]"
[fragment]="item.fragment ?? null" [fragment]="item.fragment ?? null"
[preserveFragment]="item.preserveFragment ?? false" [preserveFragment]="item.preserveFragment ?? false"
@ -65,85 +56,80 @@
[routerLinkActive]="'fuse-vertical-navigation-item-active'" [routerLinkActive]="'fuse-vertical-navigation-item-active'"
[routerLinkActiveOptions]="isActiveMatchOptions" [routerLinkActiveOptions]="isActiveMatchOptions"
[matTooltip]="item.tooltip || ''" [matTooltip]="item.tooltip || ''"
(click)="item.function(item)" (click)="item.function(item)">
>
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> <ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
</a> </a>
} </ng-container>
<!-- Item with an external link and function --> <!-- Item with an external link and function -->
@if (item.link && item.externalLink && item.function && !item.disabled) { <ng-container *ngIf="item.link && item.externalLink && item.function && !item.disabled">
<a <a
class="fuse-vertical-navigation-item" class="fuse-vertical-navigation-item"
[href]="item.link" [href]="item.link"
[target]="item.target || '_self'" [target]="item.target || '_self'"
[matTooltip]="item.tooltip || ''" [matTooltip]="item.tooltip || ''"
(click)="item.function(item)" (click)="item.function(item)">
>
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> <ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
</a> </a>
} </ng-container>
<!-- Item with a no link and no function --> <!-- Item with a no link and no function -->
@if (!item.link && !item.function && !item.disabled) { <ng-container *ngIf="!item.link && !item.function && !item.disabled">
<div <div
class="fuse-vertical-navigation-item" class="fuse-vertical-navigation-item"
[ngClass]="{ [ngClass]="{'fuse-vertical-navigation-item-active-forced': item.active}"
'fuse-vertical-navigation-item-active-forced': item.active, [matTooltip]="item.tooltip || ''">
}"
[matTooltip]="item.tooltip || ''"
>
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> <ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
</div> </div>
} </ng-container>
<!-- Item is disabled --> <!-- Item is disabled -->
@if (item.disabled) { <ng-container *ngIf="item.disabled">
<div <div
class="fuse-vertical-navigation-item fuse-vertical-navigation-item-disabled" class="fuse-vertical-navigation-item fuse-vertical-navigation-item-disabled"
[matTooltip]="item.tooltip || ''" [matTooltip]="item.tooltip || ''">
>
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container> <ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
</div> </div>
} </ng-container>
</div> </div>
<!-- Item template --> <!-- Item template -->
<ng-template #itemTemplate> <ng-template #itemTemplate>
<!-- Icon --> <!-- Icon -->
@if (item.icon) { <ng-container *ngIf="item.icon">
<mat-icon <mat-icon
class="fuse-vertical-navigation-item-icon" class="fuse-vertical-navigation-item-icon"
[ngClass]="item.classes?.icon" [ngClass]="item.classes?.icon"
[svgIcon]="item.icon" [svgIcon]="item.icon"></mat-icon>
></mat-icon> </ng-container>
}
<!-- Title & Subtitle --> <!-- Title & Subtitle -->
<div class="fuse-vertical-navigation-item-title-wrapper"> <div class="fuse-vertical-navigation-item-title-wrapper">
<div class="fuse-vertical-navigation-item-title"> <div class="fuse-vertical-navigation-item-title">
<span [ngClass]="item.classes?.title"> <span [ngClass]="item.classes?.title">
{{ item.title }} {{item.title}}
</span> </span>
</div> </div>
@if (item.subtitle) { <ng-container *ngIf="item.subtitle">
<div class="fuse-vertical-navigation-item-subtitle"> <div class="fuse-vertical-navigation-item-subtitle">
<span [ngClass]="item.classes?.subtitle"> <span [ngClass]="item.classes?.subtitle">
{{ item.subtitle }} {{item.subtitle}}
</span> </span>
</div> </div>
} </ng-container>
</div> </div>
<!-- Badge --> <!-- Badge -->
@if (item.badge) { <ng-container *ngIf="item.badge">
<div class="fuse-vertical-navigation-item-badge"> <div class="fuse-vertical-navigation-item-badge">
<div <div
class="fuse-vertical-navigation-item-badge-content" class="fuse-vertical-navigation-item-badge-content"
[ngClass]="item.badge.classes" [ngClass]="item.badge.classes">
> {{item.badge.title}}
{{ item.badge.title }}
</div> </div>
</div> </div>
} </ng-container>
</ng-template> </ng-template>

View File

@ -1,20 +1,8 @@
import { NgClass, NgTemplateOutlet } from '@angular/common'; import { NgClass, NgIf, NgTemplateOutlet } from '@angular/common';
import { import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
OnDestroy,
OnInit,
inject,
} from '@angular/core';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { import { IsActiveMatchOptions, RouterLink, RouterLinkActive } from '@angular/router';
IsActiveMatchOptions,
RouterLink,
RouterLinkActive,
} from '@angular/router';
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component'; import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component';
@ -22,38 +10,37 @@ import { FuseUtilsService } from '@fuse/services/utils/utils.service';
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'fuse-vertical-navigation-basic-item', selector : 'fuse-vertical-navigation-basic-item',
templateUrl: './basic.component.html', templateUrl : './basic.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ standalone : true,
NgClass, imports : [NgClass, NgIf, RouterLink, RouterLinkActive, MatTooltipModule, NgTemplateOutlet, MatIconModule],
RouterLink,
RouterLinkActive,
MatTooltipModule,
NgTemplateOutlet,
MatIconModule,
],
}) })
export class FuseVerticalNavigationBasicItemComponent export class FuseVerticalNavigationBasicItemComponent implements OnInit, OnDestroy
implements OnInit, OnDestroy
{ {
private _changeDetectorRef = inject(ChangeDetectorRef);
private _fuseNavigationService = inject(FuseNavigationService);
private _fuseUtilsService = inject(FuseUtilsService);
@Input() item: FuseNavigationItem; @Input() item: FuseNavigationItem;
@Input() name: string; @Input() name: string;
// Set the equivalent of {exact: false} as default for active match options. isActiveMatchOptions: IsActiveMatchOptions;
// We are not assigning the item.isActiveMatchOptions directly to the
// [routerLinkActiveOptions] because if it's "undefined" initially, the router
// will throw an error and stop working.
isActiveMatchOptions: IsActiveMatchOptions =
this._fuseUtilsService.subsetMatchOptions;
private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent; private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _fuseNavigationService: FuseNavigationService,
private _fuseUtilsService: FuseUtilsService,
)
{
// Set the equivalent of {exact: false} as default for active match options.
// We are not assigning the item.isActiveMatchOptions directly to the
// [routerLinkActiveOptions] because if it's "undefined" initially, the router
// will throw an error and stop working.
this.isActiveMatchOptions = this._fuseUtilsService.subsetMatchOptions;
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks // @ Lifecycle hooks
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -61,7 +48,8 @@ export class FuseVerticalNavigationBasicItemComponent
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Set the "isActiveMatchOptions" either from item's // Set the "isActiveMatchOptions" either from item's
// "isActiveMatchOptions" or the equivalent form of // "isActiveMatchOptions" or the equivalent form of
// item's "exactMatch" option // item's "exactMatch" option
@ -71,25 +59,26 @@ export class FuseVerticalNavigationBasicItemComponent
: this._fuseUtilsService.subsetMatchOptions; : this._fuseUtilsService.subsetMatchOptions;
// Get the parent navigation component // Get the parent navigation component
this._fuseVerticalNavigationComponent = this._fuseVerticalNavigationComponent = this._fuseNavigationService.getComponent(this.name);
this._fuseNavigationService.getComponent(this.name);
// Mark for check // Mark for check
this._changeDetectorRef.markForCheck(); this._changeDetectorRef.markForCheck();
// Subscribe to onRefreshed on the navigation component // Subscribe to onRefreshed on the navigation component
this._fuseVerticalNavigationComponent.onRefreshed this._fuseVerticalNavigationComponent.onRefreshed.pipe(
.pipe(takeUntil(this._unsubscribeAll)) takeUntil(this._unsubscribeAll),
.subscribe(() => { ).subscribe(() =>
// Mark for check {
this._changeDetectorRef.markForCheck(); // Mark for check
}); this._changeDetectorRef.markForCheck();
});
} }
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
this._unsubscribeAll.next(null); this._unsubscribeAll.next(null);
this._unsubscribeAll.complete(); this._unsubscribeAll.complete();

View File

@ -1,105 +1,106 @@
<div <div
class="fuse-vertical-navigation-item-wrapper" class="fuse-vertical-navigation-item-wrapper"
[class.fuse-vertical-navigation-item-has-subtitle]="!!item.subtitle" [class.fuse-vertical-navigation-item-has-subtitle]="!!item.subtitle"
[ngClass]="item.classes?.wrapper" [ngClass]="item.classes?.wrapper">
>
<div <div
class="fuse-vertical-navigation-item" class="fuse-vertical-navigation-item"
[ngClass]="{ 'fuse-vertical-navigation-item-disabled': item.disabled }" [ngClass]="{'fuse-vertical-navigation-item-disabled': item.disabled}"
[matTooltip]="item.tooltip || ''" [matTooltip]="item.tooltip || ''"
(click)="toggleCollapsable()" (click)="toggleCollapsable()">
>
<!-- Icon --> <!-- Icon -->
@if (item.icon) { <ng-container *ngIf="item.icon">
<mat-icon <mat-icon
class="fuse-vertical-navigation-item-icon" class="fuse-vertical-navigation-item-icon"
[ngClass]="item.classes?.icon" [ngClass]="item.classes?.icon"
[svgIcon]="item.icon" [svgIcon]="item.icon"></mat-icon>
></mat-icon> </ng-container>
}
<!-- Title & Subtitle --> <!-- Title & Subtitle -->
<div class="fuse-vertical-navigation-item-title-wrapper"> <div class="fuse-vertical-navigation-item-title-wrapper">
<div class="fuse-vertical-navigation-item-title"> <div class="fuse-vertical-navigation-item-title">
<span [ngClass]="item.classes?.title"> <span [ngClass]="item.classes?.title">
{{ item.title }} {{item.title}}
</span> </span>
</div> </div>
@if (item.subtitle) { <ng-container *ngIf="item.subtitle">
<div class="fuse-vertical-navigation-item-subtitle"> <div class="fuse-vertical-navigation-item-subtitle">
<span [ngClass]="item.classes?.subtitle"> <span [ngClass]="item.classes?.subtitle">
{{ item.subtitle }} {{item.subtitle}}
</span> </span>
</div> </div>
} </ng-container>
</div> </div>
<!-- Badge --> <!-- Badge -->
@if (item.badge) { <ng-container *ngIf="item.badge">
<div class="fuse-vertical-navigation-item-badge"> <div class="fuse-vertical-navigation-item-badge">
<div <div
class="fuse-vertical-navigation-item-badge-content" class="fuse-vertical-navigation-item-badge-content"
[ngClass]="item.badge.classes" [ngClass]="item.badge.classes">
> {{item.badge.title}}
{{ item.badge.title }}
</div> </div>
</div> </div>
} </ng-container>
<!-- Arrow --> <!-- Arrow -->
<mat-icon <mat-icon
class="fuse-vertical-navigation-item-arrow icon-size-4" class="fuse-vertical-navigation-item-arrow icon-size-4"
[svgIcon]="'heroicons_solid:chevron-right'" [svgIcon]="'heroicons_solid:chevron-right'"></mat-icon>
></mat-icon>
</div> </div>
</div> </div>
@if (!isCollapsed) { <div
<div class="fuse-vertical-navigation-item-children" @expandCollapse> class="fuse-vertical-navigation-item-children"
@for (item of item.children; track trackByFn($index, item)) { *ngIf="!isCollapsed"
<!-- Skip the hidden items --> @expandCollapse>
@if ((item.hidden && !item.hidden(item)) || !item.hidden) {
<!-- Basic -->
@if (item.type === 'basic') {
<fuse-vertical-navigation-basic-item
[item]="item"
[name]="name"
></fuse-vertical-navigation-basic-item>
}
<!-- Collapsable --> <ng-container *ngFor="let item of item.children; trackBy: trackByFn">
@if (item.type === 'collapsable') {
<fuse-vertical-navigation-collapsable-item
[item]="item"
[name]="name"
[autoCollapse]="autoCollapse"
></fuse-vertical-navigation-collapsable-item>
}
<!-- Divider --> <!-- Skip the hidden items -->
@if (item.type === 'divider') { <ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden">
<fuse-vertical-navigation-divider-item
[item]="item"
[name]="name"
></fuse-vertical-navigation-divider-item>
}
<!-- Group --> <!-- Basic -->
@if (item.type === 'group') { <ng-container *ngIf="item.type === 'basic'">
<fuse-vertical-navigation-group-item <fuse-vertical-navigation-basic-item
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-vertical-navigation-basic-item>
></fuse-vertical-navigation-group-item> </ng-container>
}
<!-- Spacer --> <!-- Collapsable -->
@if (item.type === 'spacer') { <ng-container *ngIf="item.type === 'collapsable'">
<fuse-vertical-navigation-spacer-item <fuse-vertical-navigation-collapsable-item
[item]="item" [item]="item"
[name]="name" [name]="name"
></fuse-vertical-navigation-spacer-item> [autoCollapse]="autoCollapse"></fuse-vertical-navigation-collapsable-item>
} </ng-container>
}
} <!-- Divider -->
</div> <ng-container *ngIf="item.type === 'divider'">
} <fuse-vertical-navigation-divider-item
[item]="item"
[name]="name"></fuse-vertical-navigation-divider-item>
</ng-container>
<!-- Group -->
<ng-container *ngIf="item.type === 'group'">
<fuse-vertical-navigation-group-item
[item]="item"
[name]="name"></fuse-vertical-navigation-group-item>
</ng-container>
<!-- Spacer -->
<ng-container *ngIf="item.type === 'spacer'">
<fuse-vertical-navigation-spacer-item
[item]="item"
[name]="name"></fuse-vertical-navigation-spacer-item>
</ng-container>
</ng-container>
</ng-container>
</div>

View File

@ -1,16 +1,6 @@
import { BooleanInput } from '@angular/cdk/coercion'; import { BooleanInput } from '@angular/cdk/coercion';
import { NgClass } from '@angular/common'; import { NgClass, NgFor, NgIf } from '@angular/common';
import { import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, HostBinding, Input, OnDestroy, OnInit } from '@angular/core';
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
HostBinding,
Input,
OnDestroy,
OnInit,
forwardRef,
inject,
} from '@angular/core';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { NavigationEnd, Router } from '@angular/router'; import { NavigationEnd, Router } from '@angular/router';
@ -22,35 +12,22 @@ import { FuseVerticalNavigationDividerItemComponent } from '@fuse/components/nav
import { FuseVerticalNavigationGroupItemComponent } from '@fuse/components/navigation/vertical/components/group/group.component'; import { FuseVerticalNavigationGroupItemComponent } from '@fuse/components/navigation/vertical/components/group/group.component';
import { FuseVerticalNavigationSpacerItemComponent } from '@fuse/components/navigation/vertical/components/spacer/spacer.component'; import { FuseVerticalNavigationSpacerItemComponent } from '@fuse/components/navigation/vertical/components/spacer/spacer.component';
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component'; import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component';
import { Subject, filter, takeUntil } from 'rxjs'; import { filter, Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'fuse-vertical-navigation-collapsable-item', selector : 'fuse-vertical-navigation-collapsable-item',
templateUrl: './collapsable.component.html', templateUrl : './collapsable.component.html',
animations: fuseAnimations, animations : fuseAnimations,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ standalone : true,
NgClass, imports : [NgClass, MatTooltipModule, NgIf, MatIconModule, NgFor, FuseVerticalNavigationBasicItemComponent, forwardRef(() => FuseVerticalNavigationCollapsableItemComponent), FuseVerticalNavigationDividerItemComponent, FuseVerticalNavigationGroupItemComponent, FuseVerticalNavigationSpacerItemComponent],
MatTooltipModule,
MatIconModule,
FuseVerticalNavigationBasicItemComponent,
forwardRef(() => FuseVerticalNavigationCollapsableItemComponent),
FuseVerticalNavigationDividerItemComponent,
FuseVerticalNavigationGroupItemComponent,
FuseVerticalNavigationSpacerItemComponent,
],
}) })
export class FuseVerticalNavigationCollapsableItemComponent export class FuseVerticalNavigationCollapsableItemComponent implements OnInit, OnDestroy
implements OnInit, OnDestroy
{ {
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
static ngAcceptInputType_autoCollapse: BooleanInput; static ngAcceptInputType_autoCollapse: BooleanInput;
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
private _changeDetectorRef = inject(ChangeDetectorRef);
private _router = inject(Router);
private _fuseNavigationService = inject(FuseNavigationService);
@Input() autoCollapse: boolean; @Input() autoCollapse: boolean;
@Input() item: FuseNavigationItem; @Input() item: FuseNavigationItem;
@Input() name: string; @Input() name: string;
@ -60,6 +37,17 @@ export class FuseVerticalNavigationCollapsableItemComponent
private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent; private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _router: Router,
private _fuseNavigationService: FuseNavigationService,
)
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Accessors // @ Accessors
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -67,11 +55,12 @@ export class FuseVerticalNavigationCollapsableItemComponent
/** /**
* Host binding for component classes * Host binding for component classes
*/ */
@HostBinding('class') get classList(): any { @HostBinding('class') get classList(): any
{
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
return { return {
'fuse-vertical-navigation-item-collapsed': this.isCollapsed, 'fuse-vertical-navigation-item-collapsed': this.isCollapsed,
'fuse-vertical-navigation-item-expanded': this.isExpanded, 'fuse-vertical-navigation-item-expanded' : this.isExpanded,
}; };
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
} }
@ -83,19 +72,22 @@ export class FuseVerticalNavigationCollapsableItemComponent
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Get the parent navigation component // Get the parent navigation component
this._fuseVerticalNavigationComponent = this._fuseVerticalNavigationComponent = this._fuseNavigationService.getComponent(this.name);
this._fuseNavigationService.getComponent(this.name);
// If the item has a children that has a matching url with the current url, expand... // If the item has a children that has a matching url with the current url, expand...
if (this._hasActiveChild(this.item, this._router.url)) { if ( this._hasActiveChild(this.item, this._router.url) )
{
this.expand(); this.expand();
} }
// Otherwise... // Otherwise...
else { else
{
// If the autoCollapse is on, collapse... // If the autoCollapse is on, collapse...
if (this.autoCollapse) { if ( this.autoCollapse )
{
this.collapse(); this.collapse();
} }
} }
@ -103,40 +95,49 @@ export class FuseVerticalNavigationCollapsableItemComponent
// Listen for the onCollapsableItemCollapsed from the service // Listen for the onCollapsableItemCollapsed from the service
this._fuseVerticalNavigationComponent.onCollapsableItemCollapsed this._fuseVerticalNavigationComponent.onCollapsableItemCollapsed
.pipe(takeUntil(this._unsubscribeAll)) .pipe(takeUntil(this._unsubscribeAll))
.subscribe((collapsedItem) => { .subscribe((collapsedItem) =>
{
// Check if the collapsed item is null // Check if the collapsed item is null
if (collapsedItem === null) { if ( collapsedItem === null )
{
return; return;
} }
// Collapse if this is a children of the collapsed item // Collapse if this is a children of the collapsed item
if (this._isChildrenOf(collapsedItem, this.item)) { if ( this._isChildrenOf(collapsedItem, this.item) )
{
this.collapse(); this.collapse();
} }
}); });
// Listen for the onCollapsableItemExpanded from the service if the autoCollapse is on // Listen for the onCollapsableItemExpanded from the service if the autoCollapse is on
if (this.autoCollapse) { if ( this.autoCollapse )
{
this._fuseVerticalNavigationComponent.onCollapsableItemExpanded this._fuseVerticalNavigationComponent.onCollapsableItemExpanded
.pipe(takeUntil(this._unsubscribeAll)) .pipe(takeUntil(this._unsubscribeAll))
.subscribe((expandedItem) => { .subscribe((expandedItem) =>
{
// Check if the expanded item is null // Check if the expanded item is null
if (expandedItem === null) { if ( expandedItem === null )
{
return; return;
} }
// Check if this is a parent of the expanded item // Check if this is a parent of the expanded item
if (this._isChildrenOf(this.item, expandedItem)) { if ( this._isChildrenOf(this.item, expandedItem) )
{
return; return;
} }
// Check if this has a children with a matching url with the current active url // Check if this has a children with a matching url with the current active url
if (this._hasActiveChild(this.item, this._router.url)) { if ( this._hasActiveChild(this.item, this._router.url) )
{
return; return;
} }
// Check if this is the expanded item // Check if this is the expanded item
if (this.item === expandedItem) { if ( this.item === expandedItem )
{
return; return;
} }
@ -148,39 +149,42 @@ export class FuseVerticalNavigationCollapsableItemComponent
// Attach a listener to the NavigationEnd event // Attach a listener to the NavigationEnd event
this._router.events this._router.events
.pipe( .pipe(
filter( filter((event): event is NavigationEnd => event instanceof NavigationEnd),
(event): event is NavigationEnd => takeUntil(this._unsubscribeAll),
event instanceof NavigationEnd
),
takeUntil(this._unsubscribeAll)
) )
.subscribe((event: NavigationEnd) => { .subscribe((event: NavigationEnd) =>
{
// If the item has a children that has a matching url with the current url, expand... // If the item has a children that has a matching url with the current url, expand...
if (this._hasActiveChild(this.item, event.urlAfterRedirects)) { if ( this._hasActiveChild(this.item, event.urlAfterRedirects) )
{
this.expand(); this.expand();
} }
// Otherwise... // Otherwise...
else { else
{
// If the autoCollapse is on, collapse... // If the autoCollapse is on, collapse...
if (this.autoCollapse) { if ( this.autoCollapse )
{
this.collapse(); this.collapse();
} }
} }
}); });
// Subscribe to onRefreshed on the navigation component // Subscribe to onRefreshed on the navigation component
this._fuseVerticalNavigationComponent.onRefreshed this._fuseVerticalNavigationComponent.onRefreshed.pipe(
.pipe(takeUntil(this._unsubscribeAll)) takeUntil(this._unsubscribeAll),
.subscribe(() => { ).subscribe(() =>
// Mark for check {
this._changeDetectorRef.markForCheck(); // Mark for check
}); this._changeDetectorRef.markForCheck();
});
} }
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
this._unsubscribeAll.next(null); this._unsubscribeAll.next(null);
this._unsubscribeAll.complete(); this._unsubscribeAll.complete();
@ -193,14 +197,17 @@ export class FuseVerticalNavigationCollapsableItemComponent
/** /**
* Collapse * Collapse
*/ */
collapse(): void { collapse(): void
{
// Return if the item is disabled // Return if the item is disabled
if (this.item.disabled) { if ( this.item.disabled )
{
return; return;
} }
// Return if the item is already collapsed // Return if the item is already collapsed
if (this.isCollapsed) { if ( this.isCollapsed )
{
return; return;
} }
@ -212,22 +219,23 @@ export class FuseVerticalNavigationCollapsableItemComponent
this._changeDetectorRef.markForCheck(); this._changeDetectorRef.markForCheck();
// Execute the observable // Execute the observable
this._fuseVerticalNavigationComponent.onCollapsableItemCollapsed.next( this._fuseVerticalNavigationComponent.onCollapsableItemCollapsed.next(this.item);
this.item
);
} }
/** /**
* Expand * Expand
*/ */
expand(): void { expand(): void
{
// Return if the item is disabled // Return if the item is disabled
if (this.item.disabled) { if ( this.item.disabled )
{
return; return;
} }
// Return if the item is already expanded // Return if the item is already expanded
if (!this.isCollapsed) { if ( !this.isCollapsed )
{
return; return;
} }
@ -239,19 +247,21 @@ export class FuseVerticalNavigationCollapsableItemComponent
this._changeDetectorRef.markForCheck(); this._changeDetectorRef.markForCheck();
// Execute the observable // Execute the observable
this._fuseVerticalNavigationComponent.onCollapsableItemExpanded.next( this._fuseVerticalNavigationComponent.onCollapsableItemExpanded.next(this.item);
this.item
);
} }
/** /**
* Toggle collapsable * Toggle collapsable
*/ */
toggleCollapsable(): void { toggleCollapsable(): void
{
// Toggle collapse/expand // Toggle collapse/expand
if (this.isCollapsed) { if ( this.isCollapsed )
{
this.expand(); this.expand();
} else { }
else
{
this.collapse(); this.collapse();
} }
} }
@ -262,7 +272,8 @@ export class FuseVerticalNavigationCollapsableItemComponent
* @param index * @param index
* @param item * @param item
*/ */
trackByFn(index: number, item: any): any { trackByFn(index: number, item: any): any
{
return item.id || index; return item.id || index;
} }
@ -278,28 +289,28 @@ export class FuseVerticalNavigationCollapsableItemComponent
* @param currentUrl * @param currentUrl
* @private * @private
*/ */
private _hasActiveChild( private _hasActiveChild(item: FuseNavigationItem, currentUrl: string): boolean
item: FuseNavigationItem, {
currentUrl: string
): boolean {
const children = item.children; const children = item.children;
if (!children) { if ( !children )
{
return false; return false;
} }
for (const child of children) { for ( const child of children )
if (child.children) { {
if (this._hasActiveChild(child, currentUrl)) { if ( child.children )
{
if ( this._hasActiveChild(child, currentUrl) )
{
return true; return true;
} }
} }
// Check if the child has a link and is active // Check if the child has a link and is active
if ( if ( child.link && this._router.isActive(child.link, child.exactMatch || false) )
child.link && {
this._router.isActive(child.link, child.exactMatch || false)
) {
return true; return true;
} }
} }
@ -315,23 +326,26 @@ export class FuseVerticalNavigationCollapsableItemComponent
* @param item * @param item
* @private * @private
*/ */
private _isChildrenOf( private _isChildrenOf(parent: FuseNavigationItem, item: FuseNavigationItem): boolean
parent: FuseNavigationItem, {
item: FuseNavigationItem
): boolean {
const children = parent.children; const children = parent.children;
if (!children) { if ( !children )
{
return false; return false;
} }
if (children.indexOf(item) > -1) { if ( children.indexOf(item) > -1 )
{
return true; return true;
} }
for (const child of children) { for ( const child of children )
if (child.children) { {
if (this._isChildrenOf(child, item)) { if ( child.children )
{
if ( this._isChildrenOf(child, item) )
{
return true; return true;
} }
} }

View File

@ -1,5 +1,4 @@
<!-- Divider --> <!-- Divider -->
<div <div
class="fuse-vertical-navigation-item-wrapper divider" class="fuse-vertical-navigation-item-wrapper divider"
[ngClass]="item.classes?.wrapper" [ngClass]="item.classes?.wrapper"></div>
></div>

View File

@ -1,36 +1,35 @@
import { NgClass } from '@angular/common'; import { NgClass } from '@angular/common';
import { import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
inject,
Input,
OnDestroy,
OnInit,
} from '@angular/core';
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component'; import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component';
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'fuse-vertical-navigation-divider-item', selector : 'fuse-vertical-navigation-divider-item',
templateUrl: './divider.component.html', templateUrl : './divider.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgClass], standalone : true,
imports : [NgClass],
}) })
export class FuseVerticalNavigationDividerItemComponent export class FuseVerticalNavigationDividerItemComponent implements OnInit, OnDestroy
implements OnInit, OnDestroy
{ {
private _changeDetectorRef = inject(ChangeDetectorRef);
private _fuseNavigationService = inject(FuseNavigationService);
@Input() item: FuseNavigationItem; @Input() item: FuseNavigationItem;
@Input() name: string; @Input() name: string;
private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent; private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _fuseNavigationService: FuseNavigationService,
)
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks // @ Lifecycle hooks
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -38,24 +37,26 @@ export class FuseVerticalNavigationDividerItemComponent
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Get the parent navigation component // Get the parent navigation component
this._fuseVerticalNavigationComponent = this._fuseVerticalNavigationComponent = this._fuseNavigationService.getComponent(this.name);
this._fuseNavigationService.getComponent(this.name);
// Subscribe to onRefreshed on the navigation component // Subscribe to onRefreshed on the navigation component
this._fuseVerticalNavigationComponent.onRefreshed this._fuseVerticalNavigationComponent.onRefreshed.pipe(
.pipe(takeUntil(this._unsubscribeAll)) takeUntil(this._unsubscribeAll),
.subscribe(() => { ).subscribe(() =>
// Mark for check {
this._changeDetectorRef.markForCheck(); // Mark for check
}); this._changeDetectorRef.markForCheck();
});
} }
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
this._unsubscribeAll.next(null); this._unsubscribeAll.next(null);
this._unsubscribeAll.complete(); this._unsubscribeAll.complete();

View File

@ -2,90 +2,90 @@
<div <div
class="fuse-vertical-navigation-item-wrapper" class="fuse-vertical-navigation-item-wrapper"
[class.fuse-vertical-navigation-item-has-subtitle]="!!item.subtitle" [class.fuse-vertical-navigation-item-has-subtitle]="!!item.subtitle"
[ngClass]="item.classes?.wrapper" [ngClass]="item.classes?.wrapper">
>
<div class="fuse-vertical-navigation-item"> <div class="fuse-vertical-navigation-item">
<!-- Icon --> <!-- Icon -->
@if (item.icon) { <ng-container *ngIf="item.icon">
<mat-icon <mat-icon
class="fuse-vertical-navigation-item-icon" class="fuse-vertical-navigation-item-icon"
[ngClass]="item.classes?.icon" [ngClass]="item.classes?.icon"
[svgIcon]="item.icon" [svgIcon]="item.icon"></mat-icon>
></mat-icon> </ng-container>
}
<!-- Title & Subtitle --> <!-- Title & Subtitle -->
<div class="fuse-vertical-navigation-item-title-wrapper"> <div class="fuse-vertical-navigation-item-title-wrapper">
<div class="fuse-vertical-navigation-item-title"> <div class="fuse-vertical-navigation-item-title">
<span [ngClass]="item.classes?.title"> <span [ngClass]="item.classes?.title">
{{ item.title }} {{item.title}}
</span> </span>
</div> </div>
@if (item.subtitle) { <ng-container *ngIf="item.subtitle">
<div class="fuse-vertical-navigation-item-subtitle"> <div class="fuse-vertical-navigation-item-subtitle">
<span [ngClass]="item.classes?.subtitle"> <span [ngClass]="item.classes?.subtitle">
{{ item.subtitle }} {{item.subtitle}}
</span> </span>
</div> </div>
} </ng-container>
</div> </div>
<!-- Badge --> <!-- Badge -->
@if (item.badge) { <ng-container *ngIf="item.badge">
<div class="fuse-vertical-navigation-item-badge"> <div class="fuse-vertical-navigation-item-badge">
<div <div
class="fuse-vertical-navigation-item-badge-content" class="fuse-vertical-navigation-item-badge-content"
[ngClass]="item.badge.classes" [ngClass]="item.badge.classes">
> {{item.badge.title}}
{{ item.badge.title }}
</div> </div>
</div> </div>
} </ng-container>
</div> </div>
</div> </div>
@for (item of item.children; track trackByFn($index, item)) { <ng-container *ngFor="let item of item.children; trackBy: trackByFn">
<!-- Skip the hidden items --> <!-- Skip the hidden items -->
@if ((item.hidden && !item.hidden(item)) || !item.hidden) { <ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden">
<!-- Basic --> <!-- Basic -->
@if (item.type === 'basic') { <ng-container *ngIf="item.type === 'basic'">
<fuse-vertical-navigation-basic-item <fuse-vertical-navigation-basic-item
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-vertical-navigation-basic-item>
></fuse-vertical-navigation-basic-item> </ng-container>
}
<!-- Collapsable --> <!-- Collapsable -->
@if (item.type === 'collapsable') { <ng-container *ngIf="item.type === 'collapsable'">
<fuse-vertical-navigation-collapsable-item <fuse-vertical-navigation-collapsable-item
[item]="item" [item]="item"
[name]="name" [name]="name"
[autoCollapse]="autoCollapse" [autoCollapse]="autoCollapse"></fuse-vertical-navigation-collapsable-item>
></fuse-vertical-navigation-collapsable-item> </ng-container>
}
<!-- Divider --> <!-- Divider -->
@if (item.type === 'divider') { <ng-container *ngIf="item.type === 'divider'">
<fuse-vertical-navigation-divider-item <fuse-vertical-navigation-divider-item
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-vertical-navigation-divider-item>
></fuse-vertical-navigation-divider-item> </ng-container>
}
<!-- Group --> <!-- Group -->
@if (item.type === 'group') { <ng-container *ngIf="item.type === 'group'">
<fuse-vertical-navigation-group-item <fuse-vertical-navigation-group-item
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-vertical-navigation-group-item>
></fuse-vertical-navigation-group-item> </ng-container>
}
<!-- Spacer --> <!-- Spacer -->
@if (item.type === 'spacer') { <ng-container *ngIf="item.type === 'spacer'">
<fuse-vertical-navigation-spacer-item <fuse-vertical-navigation-spacer-item
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-vertical-navigation-spacer-item>
></fuse-vertical-navigation-spacer-item> </ng-container>
}
} </ng-container>
}
</ng-container>

View File

@ -1,15 +1,6 @@
import { BooleanInput } from '@angular/cdk/coercion'; import { BooleanInput } from '@angular/cdk/coercion';
import { NgClass } from '@angular/common'; import { NgClass, NgFor, NgIf } from '@angular/common';
import { import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
OnDestroy,
OnInit,
forwardRef,
inject,
} from '@angular/core';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
@ -21,29 +12,18 @@ import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/ver
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'fuse-vertical-navigation-group-item', selector : 'fuse-vertical-navigation-group-item',
templateUrl: './group.component.html', templateUrl : './group.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ standalone : true,
NgClass, imports : [NgClass, NgIf, MatIconModule, NgFor, FuseVerticalNavigationBasicItemComponent, FuseVerticalNavigationCollapsableItemComponent, FuseVerticalNavigationDividerItemComponent, forwardRef(() => FuseVerticalNavigationGroupItemComponent), FuseVerticalNavigationSpacerItemComponent],
MatIconModule,
FuseVerticalNavigationBasicItemComponent,
FuseVerticalNavigationCollapsableItemComponent,
FuseVerticalNavigationDividerItemComponent,
forwardRef(() => FuseVerticalNavigationGroupItemComponent),
FuseVerticalNavigationSpacerItemComponent,
],
}) })
export class FuseVerticalNavigationGroupItemComponent export class FuseVerticalNavigationGroupItemComponent implements OnInit, OnDestroy
implements OnInit, OnDestroy
{ {
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
static ngAcceptInputType_autoCollapse: BooleanInput; static ngAcceptInputType_autoCollapse: BooleanInput;
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
private _changeDetectorRef = inject(ChangeDetectorRef);
private _fuseNavigationService = inject(FuseNavigationService);
@Input() autoCollapse: boolean; @Input() autoCollapse: boolean;
@Input() item: FuseNavigationItem; @Input() item: FuseNavigationItem;
@Input() name: string; @Input() name: string;
@ -51,6 +31,16 @@ export class FuseVerticalNavigationGroupItemComponent
private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent; private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _fuseNavigationService: FuseNavigationService,
)
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks // @ Lifecycle hooks
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -58,24 +48,26 @@ export class FuseVerticalNavigationGroupItemComponent
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Get the parent navigation component // Get the parent navigation component
this._fuseVerticalNavigationComponent = this._fuseVerticalNavigationComponent = this._fuseNavigationService.getComponent(this.name);
this._fuseNavigationService.getComponent(this.name);
// Subscribe to onRefreshed on the navigation component // Subscribe to onRefreshed on the navigation component
this._fuseVerticalNavigationComponent.onRefreshed this._fuseVerticalNavigationComponent.onRefreshed.pipe(
.pipe(takeUntil(this._unsubscribeAll)) takeUntil(this._unsubscribeAll),
.subscribe(() => { ).subscribe(() =>
// Mark for check {
this._changeDetectorRef.markForCheck(); // Mark for check
}); this._changeDetectorRef.markForCheck();
});
} }
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
this._unsubscribeAll.next(null); this._unsubscribeAll.next(null);
this._unsubscribeAll.complete(); this._unsubscribeAll.complete();
@ -91,7 +83,8 @@ export class FuseVerticalNavigationGroupItemComponent
* @param index * @param index
* @param item * @param item
*/ */
trackByFn(index: number, item: any): any { trackByFn(index: number, item: any): any
{
return item.id || index; return item.id || index;
} }
} }

View File

@ -1,5 +1,4 @@
<!-- Spacer --> <!-- Spacer -->
<div <div
class="fuse-vertical-navigation-item-wrapper" class="fuse-vertical-navigation-item-wrapper"
[ngClass]="item.classes?.wrapper" [ngClass]="item.classes?.wrapper"></div>
></div>

View File

@ -1,36 +1,35 @@
import { NgClass } from '@angular/common'; import { NgClass } from '@angular/common';
import { import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
inject,
Input,
OnDestroy,
OnInit,
} from '@angular/core';
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types'; import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component'; import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component';
import { Subject, takeUntil } from 'rxjs'; import { Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'fuse-vertical-navigation-spacer-item', selector : 'fuse-vertical-navigation-spacer-item',
templateUrl: './spacer.component.html', templateUrl : './spacer.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgClass], standalone : true,
imports : [NgClass],
}) })
export class FuseVerticalNavigationSpacerItemComponent export class FuseVerticalNavigationSpacerItemComponent implements OnInit, OnDestroy
implements OnInit, OnDestroy
{ {
private _changeDetectorRef = inject(ChangeDetectorRef);
private _fuseNavigationService = inject(FuseNavigationService);
@Input() item: FuseNavigationItem; @Input() item: FuseNavigationItem;
@Input() name: string; @Input() name: string;
private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent; private _fuseVerticalNavigationComponent: FuseVerticalNavigationComponent;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _fuseNavigationService: FuseNavigationService,
)
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks // @ Lifecycle hooks
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -38,24 +37,26 @@ export class FuseVerticalNavigationSpacerItemComponent
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Get the parent navigation component // Get the parent navigation component
this._fuseVerticalNavigationComponent = this._fuseVerticalNavigationComponent = this._fuseNavigationService.getComponent(this.name);
this._fuseNavigationService.getComponent(this.name);
// Subscribe to onRefreshed on the navigation component // Subscribe to onRefreshed on the navigation component
this._fuseVerticalNavigationComponent.onRefreshed this._fuseVerticalNavigationComponent.onRefreshed.pipe(
.pipe(takeUntil(this._unsubscribeAll)) takeUntil(this._unsubscribeAll),
.subscribe(() => { ).subscribe(() =>
// Mark for check {
this._changeDetectorRef.markForCheck(); // Mark for check
}); this._changeDetectorRef.markForCheck();
});
} }
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
this._unsubscribeAll.next(null); this._unsubscribeAll.next(null);
this._unsubscribeAll.complete(); this._unsubscribeAll.complete();

View File

@ -4,6 +4,7 @@
} }
fuse-vertical-navigation { fuse-vertical-navigation {
/* Compact appearance overrides */ /* Compact appearance overrides */
&.fuse-vertical-navigation-appearance-compact { &.fuse-vertical-navigation-appearance-compact {
width: var(--fuse-vertical-navigation-compact-width); width: var(--fuse-vertical-navigation-compact-width);
@ -12,11 +13,10 @@ fuse-vertical-navigation {
/* Left positioned */ /* Left positioned */
&.fuse-vertical-navigation-position-left { &.fuse-vertical-navigation-position-left {
/* Side mode */ /* Side mode */
&.fuse-vertical-navigation-mode-side { &.fuse-vertical-navigation-mode-side {
margin-left: calc( margin-left: calc(var(--fuse-vertical-navigation-compact-width) * -1);
var(--fuse-vertical-navigation-compact-width) * -1
);
} }
/* Opened */ /* Opened */
@ -27,11 +27,10 @@ fuse-vertical-navigation {
/* Right positioned */ /* Right positioned */
&.fuse-vertical-navigation-position-right { &.fuse-vertical-navigation-position-right {
/* Side mode */ /* Side mode */
&.fuse-vertical-navigation-mode-side { &.fuse-vertical-navigation-mode-side {
margin-right: calc( margin-right: calc(var(--fuse-vertical-navigation-compact-width) * -1);
var(--fuse-vertical-navigation-compact-width) * -1
);
} }
/* Opened */ /* Opened */
@ -48,10 +47,13 @@ fuse-vertical-navigation {
/* Wrapper */ /* Wrapper */
.fuse-vertical-navigation-wrapper { .fuse-vertical-navigation-wrapper {
/* Content */ /* Content */
.fuse-vertical-navigation-content { .fuse-vertical-navigation-content {
> fuse-vertical-navigation-aside-item, > fuse-vertical-navigation-aside-item,
> fuse-vertical-navigation-basic-item { > fuse-vertical-navigation-basic-item {
.fuse-vertical-navigation-item-wrapper { .fuse-vertical-navigation-item-wrapper {
margin: 4px 8px 0 8px; margin: 4px 8px 0 8px;
@ -89,12 +91,13 @@ fuse-vertical-navigation {
} }
> fuse-vertical-navigation-collapsable-item { > fuse-vertical-navigation-collapsable-item {
display: none; display: none
} }
> fuse-vertical-navigation-group-item { > fuse-vertical-navigation-group-item {
> .fuse-vertical-navigation-item-wrapper { > .fuse-vertical-navigation-item-wrapper {
display: none; display: none
} }
} }
} }

View File

@ -25,8 +25,7 @@ fuse-vertical-navigation {
&.fuse-vertical-navigation-animations-enabled { &.fuse-vertical-navigation-animations-enabled {
transition-duration: 400ms; transition-duration: 400ms;
transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1); transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1);
transition-property: visibility, margin-left, margin-right, transform, transition-property: visibility, margin-left, margin-right, transform, width, max-width, min-width;
width, max-width, min-width;
/* Wrapper */ /* Wrapper */
.fuse-vertical-navigation-wrapper { .fuse-vertical-navigation-wrapper {
@ -45,6 +44,7 @@ fuse-vertical-navigation {
/* Left position */ /* Left position */
&.fuse-vertical-navigation-position-left { &.fuse-vertical-navigation-position-left {
/* Side mode */ /* Side mode */
&.fuse-vertical-navigation-mode-side { &.fuse-vertical-navigation-mode-side {
margin-left: calc(#{var(--fuse-vertical-navigation-width)} * -1); margin-left: calc(#{var(--fuse-vertical-navigation-width)} * -1);
@ -72,6 +72,7 @@ fuse-vertical-navigation {
/* Right position */ /* Right position */
&.fuse-vertical-navigation-position-right { &.fuse-vertical-navigation-position-right {
/* Side mode */ /* Side mode */
&.fuse-vertical-navigation-mode-side { &.fuse-vertical-navigation-mode-side {
margin-right: calc(var(--fuse-vertical-navigation-width) * -1); margin-right: calc(var(--fuse-vertical-navigation-width) * -1);
@ -136,6 +137,7 @@ fuse-vertical-navigation {
/* Header */ /* Header */
.fuse-vertical-navigation-header { .fuse-vertical-navigation-header {
} }
/* Content */ /* Content */
@ -158,6 +160,7 @@ fuse-vertical-navigation {
/* Footer */ /* Footer */
.fuse-vertical-navigation-footer { .fuse-vertical-navigation-footer {
} }
} }
@ -192,6 +195,7 @@ fuse-vertical-navigation {
} }
&.fuse-vertical-navigation-position-right { &.fuse-vertical-navigation-position-right {
.fuse-vertical-navigation-aside-wrapper { .fuse-vertical-navigation-aside-wrapper {
left: auto; left: auto;
right: var(--fuse-vertical-navigation-width); right: var(--fuse-vertical-navigation-width);
@ -215,6 +219,7 @@ fuse-vertical-navigation {
user-select: none; user-select: none;
.fuse-vertical-navigation-item-wrapper { .fuse-vertical-navigation-item-wrapper {
.fuse-vertical-navigation-item { .fuse-vertical-navigation-item {
position: relative; position: relative;
display: flex; display: flex;
@ -238,6 +243,7 @@ fuse-vertical-navigation {
} }
.fuse-vertical-navigation-item-title-wrapper { .fuse-vertical-navigation-item-title-wrapper {
.fuse-vertical-navigation-item-subtitle { .fuse-vertical-navigation-item-subtitle {
font-size: 11px; font-size: 11px;
line-height: 1.5; line-height: 1.5;
@ -266,6 +272,7 @@ fuse-vertical-navigation {
fuse-vertical-navigation-basic-item, fuse-vertical-navigation-basic-item,
fuse-vertical-navigation-collapsable-item, fuse-vertical-navigation-collapsable-item,
fuse-vertical-navigation-group-item { fuse-vertical-navigation-group-item {
> .fuse-vertical-navigation-item-wrapper { > .fuse-vertical-navigation-item-wrapper {
margin: 0 12px; margin: 0 12px;
} }
@ -284,17 +291,23 @@ fuse-vertical-navigation {
/* Aside */ /* Aside */
fuse-vertical-navigation-aside-item { fuse-vertical-navigation-aside-item {
} }
/* Basic */ /* Basic */
fuse-vertical-navigation-basic-item { fuse-vertical-navigation-basic-item {
} }
/* Collapsable */ /* Collapsable */
fuse-vertical-navigation-collapsable-item { fuse-vertical-navigation-collapsable-item {
> .fuse-vertical-navigation-item-wrapper { > .fuse-vertical-navigation-item-wrapper {
.fuse-vertical-navigation-item { .fuse-vertical-navigation-item {
.fuse-vertical-navigation-item-badge { .fuse-vertical-navigation-item-badge {
+ .fuse-vertical-navigation-item-arrow { + .fuse-vertical-navigation-item-arrow {
margin-left: 8px; margin-left: 8px;
} }
@ -304,16 +317,18 @@ fuse-vertical-navigation {
height: 20px; height: 20px;
line-height: 20px; line-height: 20px;
margin-left: auto; margin-left: auto;
transition: transition: transform 300ms cubic-bezier(0.25, 0.8, 0.25, 1),
transform 300ms cubic-bezier(0.25, 0.8, 0.25, 1), color 375ms cubic-bezier(0.25, 0.8, 0.25, 1);
color 375ms cubic-bezier(0.25, 0.8, 0.25, 1);
} }
} }
} }
&.fuse-vertical-navigation-item-expanded { &.fuse-vertical-navigation-item-expanded {
> .fuse-vertical-navigation-item-wrapper { > .fuse-vertical-navigation-item-wrapper {
.fuse-vertical-navigation-item { .fuse-vertical-navigation-item {
.fuse-vertical-navigation-item-arrow { .fuse-vertical-navigation-item-arrow {
transform: rotate(90deg); transform: rotate(90deg);
} }
@ -322,6 +337,7 @@ fuse-vertical-navigation {
} }
> .fuse-vertical-navigation-item-children { > .fuse-vertical-navigation-item-children {
> *:first-child { > *:first-child {
margin-top: 6px; margin-top: 6px;
} }
@ -330,6 +346,7 @@ fuse-vertical-navigation {
padding-bottom: 6px; padding-bottom: 6px;
> .fuse-vertical-navigation-item-children { > .fuse-vertical-navigation-item-children {
> *:last-child { > *:last-child {
padding-bottom: 0; padding-bottom: 0;
} }
@ -351,18 +368,21 @@ fuse-vertical-navigation {
/* 2nd level */ /* 2nd level */
.fuse-vertical-navigation-item-children { .fuse-vertical-navigation-item-children {
.fuse-vertical-navigation-item { .fuse-vertical-navigation-item {
padding-left: 72px; padding-left: 72px;
} }
/* 3rd level */ /* 3rd level */
.fuse-vertical-navigation-item-children { .fuse-vertical-navigation-item-children {
.fuse-vertical-navigation-item { .fuse-vertical-navigation-item {
padding-left: 88px; padding-left: 88px;
} }
/* 4th level */ /* 4th level */
.fuse-vertical-navigation-item-children { .fuse-vertical-navigation-item-children {
.fuse-vertical-navigation-item { .fuse-vertical-navigation-item {
padding-left: 104px; padding-left: 104px;
} }
@ -384,14 +404,18 @@ fuse-vertical-navigation {
/* Group */ /* Group */
fuse-vertical-navigation-group-item { fuse-vertical-navigation-group-item {
> .fuse-vertical-navigation-item-wrapper { > .fuse-vertical-navigation-item-wrapper {
.fuse-vertical-navigation-item { .fuse-vertical-navigation-item {
.fuse-vertical-navigation-item-badge, .fuse-vertical-navigation-item-badge,
.fuse-vertical-navigation-item-icon { .fuse-vertical-navigation-item-icon {
display: none !important; display: none !important;
} }
.fuse-vertical-navigation-item-title-wrapper { .fuse-vertical-navigation-item-title-wrapper {
.fuse-vertical-navigation-item-title { .fuse-vertical-navigation-item-title {
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
@ -450,7 +474,9 @@ fuse-vertical-navigation-aside-item,
fuse-vertical-navigation-basic-item, fuse-vertical-navigation-basic-item,
fuse-vertical-navigation-collapsable-item, fuse-vertical-navigation-collapsable-item,
fuse-vertical-navigation-group-item { fuse-vertical-navigation-group-item {
.fuse-vertical-navigation-item-wrapper { .fuse-vertical-navigation-item-wrapper {
.fuse-vertical-navigation-item { .fuse-vertical-navigation-item {
color: currentColor; color: currentColor;
@ -459,6 +485,7 @@ fuse-vertical-navigation-group-item {
} }
.fuse-vertical-navigation-item-title-wrapper { .fuse-vertical-navigation-item-title-wrapper {
.fuse-vertical-navigation-item-title { .fuse-vertical-navigation-item-title {
@apply text-current opacity-80; @apply text-current opacity-80;
} }
@ -475,10 +502,14 @@ fuse-vertical-navigation-group-item {
fuse-vertical-navigation-aside-item, fuse-vertical-navigation-aside-item,
fuse-vertical-navigation-basic-item, fuse-vertical-navigation-basic-item,
fuse-vertical-navigation-collapsable-item { fuse-vertical-navigation-collapsable-item {
> .fuse-vertical-navigation-item-wrapper { > .fuse-vertical-navigation-item-wrapper {
.fuse-vertical-navigation-item { .fuse-vertical-navigation-item {
/* Active state */ /* Active state */
&:not(.fuse-vertical-navigation-item-disabled) { &:not(.fuse-vertical-navigation-item-disabled) {
&.fuse-vertical-navigation-item-active, &.fuse-vertical-navigation-item-active,
&.fuse-vertical-navigation-item-active-forced { &.fuse-vertical-navigation-item-active-forced {
@apply bg-gray-800 bg-opacity-5 dark:bg-white dark:bg-opacity-12; @apply bg-gray-800 bg-opacity-5 dark:bg-white dark:bg-opacity-12;
@ -498,9 +529,8 @@ fuse-vertical-navigation-collapsable-item {
} }
/* Hover state */ /* Hover state */
&:not(.fuse-vertical-navigation-item-active-forced):not( &:not(.fuse-vertical-navigation-item-active-forced):not(.fuse-vertical-navigation-item-active):not(.fuse-vertical-navigation-item-disabled) {
.fuse-vertical-navigation-item-active
):not(.fuse-vertical-navigation-item-disabled) {
&:hover { &:hover {
@apply bg-gray-800 bg-opacity-5 dark:bg-white dark:bg-opacity-12; @apply bg-gray-800 bg-opacity-5 dark:bg-white dark:bg-opacity-12;
@ -524,10 +554,14 @@ fuse-vertical-navigation-collapsable-item {
/* Collapsable */ /* Collapsable */
fuse-vertical-navigation-collapsable-item { fuse-vertical-navigation-collapsable-item {
/* Expanded state */ /* Expanded state */
&.fuse-vertical-navigation-item-expanded { &.fuse-vertical-navigation-item-expanded {
> .fuse-vertical-navigation-item-wrapper { > .fuse-vertical-navigation-item-wrapper {
.fuse-vertical-navigation-item { .fuse-vertical-navigation-item {
.fuse-vertical-navigation-item-icon { .fuse-vertical-navigation-item-icon {
@apply opacity-100; @apply opacity-100;
} }
@ -547,11 +581,15 @@ fuse-vertical-navigation-collapsable-item {
/* Group */ /* Group */
fuse-vertical-navigation-group-item { fuse-vertical-navigation-group-item {
> .fuse-vertical-navigation-item-wrapper { > .fuse-vertical-navigation-item-wrapper {
.fuse-vertical-navigation-item { .fuse-vertical-navigation-item {
.fuse-vertical-navigation-item-title-wrapper { .fuse-vertical-navigation-item-title-wrapper {
.fuse-vertical-navigation-item-title { .fuse-vertical-navigation-item-title {
@apply text-primary-600 opacity-100 dark:text-primary-400; @apply opacity-100 text-primary-600 dark:text-primary-400;
} }
} }
} }

View File

@ -5,8 +5,10 @@
} }
fuse-vertical-navigation { fuse-vertical-navigation {
/* Dense appearance overrides */ /* Dense appearance overrides */
&.fuse-vertical-navigation-appearance-dense { &.fuse-vertical-navigation-appearance-dense {
&:not(.fuse-vertical-navigation-mode-over) { &:not(.fuse-vertical-navigation-mode-over) {
width: var(--fuse-vertical-navigation-dense-width); width: var(--fuse-vertical-navigation-dense-width);
min-width: var(--fuse-vertical-navigation-dense-width); min-width: var(--fuse-vertical-navigation-dense-width);
@ -14,11 +16,10 @@ fuse-vertical-navigation {
/* Left positioned */ /* Left positioned */
&.fuse-vertical-navigation-position-left { &.fuse-vertical-navigation-position-left {
/* Side mode */ /* Side mode */
&.fuse-vertical-navigation-mode-side { &.fuse-vertical-navigation-mode-side {
margin-left: calc( margin-left: calc(var(--fuse-vertical-navigation-dense-width) * -1);
var(--fuse-vertical-navigation-dense-width) * -1
);
} }
/* Opened */ /* Opened */
@ -29,11 +30,10 @@ fuse-vertical-navigation {
/* Right positioned */ /* Right positioned */
&.fuse-vertical-navigation-position-right { &.fuse-vertical-navigation-position-right {
/* Side mode */ /* Side mode */
&.fuse-vertical-navigation-mode-side { &.fuse-vertical-navigation-mode-side {
margin-right: calc( margin-right: calc(var(--fuse-vertical-navigation-dense-width) * -1);
var(--fuse-vertical-navigation-dense-width) * -1
);
} }
/* Opened */ /* Opened */
@ -48,6 +48,7 @@ fuse-vertical-navigation {
} }
&.fuse-vertical-navigation-hover { &.fuse-vertical-navigation-hover {
.fuse-vertical-navigation-aside-wrapper { .fuse-vertical-navigation-aside-wrapper {
left: auto; left: auto;
right: var(--fuse-vertical-navigation-width); right: var(--fuse-vertical-navigation-width);
@ -58,38 +59,33 @@ fuse-vertical-navigation {
/* Wrapper */ /* Wrapper */
.fuse-vertical-navigation-wrapper { .fuse-vertical-navigation-wrapper {
/* Content */ /* Content */
.fuse-vertical-navigation-content { .fuse-vertical-navigation-content {
fuse-vertical-navigation-aside-item, fuse-vertical-navigation-aside-item,
fuse-vertical-navigation-basic-item, fuse-vertical-navigation-basic-item,
fuse-vertical-navigation-collapsable-item, fuse-vertical-navigation-collapsable-item,
fuse-vertical-navigation-group-item { fuse-vertical-navigation-group-item {
.fuse-vertical-navigation-item-wrapper { .fuse-vertical-navigation-item-wrapper {
.fuse-vertical-navigation-item { .fuse-vertical-navigation-item {
width: calc( width: calc(var(--fuse-vertical-navigation-dense-width) - 24px);
var(--fuse-vertical-navigation-dense-width) - min-width: calc(var(--fuse-vertical-navigation-dense-width) - 24px);
24px max-width: calc(var(--fuse-vertical-navigation-dense-width) - 24px);
);
min-width: calc(
var(--fuse-vertical-navigation-dense-width) -
24px
);
max-width: calc(
var(--fuse-vertical-navigation-dense-width) -
24px
);
.fuse-vertical-navigation-item-arrow, .fuse-vertical-navigation-item-arrow,
.fuse-vertical-navigation-item-badge, .fuse-vertical-navigation-item-badge,
.fuse-vertical-navigation-item-title-wrapper { .fuse-vertical-navigation-item-title-wrapper {
transition: opacity 400ms transition: opacity 400ms cubic-bezier(0.25, 0.8, 0.25, 1);
cubic-bezier(0.25, 0.8, 0.25, 1);
} }
} }
} }
} }
fuse-vertical-navigation-group-item { fuse-vertical-navigation-group-item {
&:first-of-type { &:first-of-type {
margin-top: 0; margin-top: 0;
} }
@ -97,14 +93,16 @@ fuse-vertical-navigation {
} }
} }
&:not(.fuse-vertical-navigation-hover):not( &:not(.fuse-vertical-navigation-hover):not(.fuse-vertical-navigation-mode-over) {
.fuse-vertical-navigation-mode-over
) {
/* Wrapper */ /* Wrapper */
.fuse-vertical-navigation-wrapper { .fuse-vertical-navigation-wrapper {
/* Content */ /* Content */
.fuse-vertical-navigation-content { .fuse-vertical-navigation-content {
.fuse-vertical-navigation-item-wrapper { .fuse-vertical-navigation-item-wrapper {
.fuse-vertical-navigation-item { .fuse-vertical-navigation-item {
padding: 10px 16px; padding: 10px 16px;
@ -118,14 +116,18 @@ fuse-vertical-navigation {
} }
fuse-vertical-navigation-collapsable-item { fuse-vertical-navigation-collapsable-item {
.fuse-vertical-navigation-item-children { .fuse-vertical-navigation-item-children {
display: none; display: none;
} }
} }
fuse-vertical-navigation-group-item { fuse-vertical-navigation-group-item {
> .fuse-vertical-navigation-item-wrapper { > .fuse-vertical-navigation-item-wrapper {
.fuse-vertical-navigation-item { .fuse-vertical-navigation-item {
&:before { &:before {
content: ''; content: '';
position: absolute; position: absolute;
@ -147,28 +149,24 @@ fuse-vertical-navigation {
/* Hover */ /* Hover */
&.fuse-vertical-navigation-hover { &.fuse-vertical-navigation-hover {
.fuse-vertical-navigation-wrapper { .fuse-vertical-navigation-wrapper {
width: var(--fuse-vertical-navigation-width); width: var(--fuse-vertical-navigation-width);
.fuse-vertical-navigation-content { .fuse-vertical-navigation-content {
.fuse-vertical-navigation-item-wrapper { .fuse-vertical-navigation-item-wrapper {
.fuse-vertical-navigation-item { .fuse-vertical-navigation-item {
width: calc( width: calc(var(--fuse-vertical-navigation-width) - 24px);
var(--fuse-vertical-navigation-width) - 24px min-width: calc(var(--fuse-vertical-navigation-width) - 24px);
); max-width: calc(var(--fuse-vertical-navigation-width) - 24px);
min-width: calc(
var(--fuse-vertical-navigation-width) - 24px
);
max-width: calc(
var(--fuse-vertical-navigation-width) - 24px
);
.fuse-vertical-navigation-item-arrow, .fuse-vertical-navigation-item-arrow,
.fuse-vertical-navigation-item-badge, .fuse-vertical-navigation-item-badge,
.fuse-vertical-navigation-item-title-wrapper { .fuse-vertical-navigation-item-title-wrapper {
white-space: nowrap; white-space: nowrap;
animation: removeWhiteSpaceNoWrap 1ms linear animation: removeWhiteSpaceNoWrap 1ms linear 350ms;
350ms;
animation-fill-mode: forwards; animation-fill-mode: forwards;
} }
} }
@ -185,10 +183,10 @@ fuse-vertical-navigation {
@keyframes removeWhiteSpaceNoWrap { @keyframes removeWhiteSpaceNoWrap {
0% { 0% {
white-space: nowrap; white-space: nowrap
} }
99% { 99% {
white-space: nowrap; white-space: nowrap
} }
100% { 100% {
white-space: normal; white-space: normal;

View File

@ -4,6 +4,7 @@
} }
fuse-vertical-navigation { fuse-vertical-navigation {
/* Thin appearance overrides */ /* Thin appearance overrides */
&.fuse-vertical-navigation-appearance-thin { &.fuse-vertical-navigation-appearance-thin {
width: var(--fuse-vertical-navigation-thin-width); width: var(--fuse-vertical-navigation-thin-width);
@ -12,10 +13,9 @@ fuse-vertical-navigation {
/* Left positioned */ /* Left positioned */
&.fuse-vertical-navigation-position-left { &.fuse-vertical-navigation-position-left {
&.fuse-vertical-navigation-mode-side { &.fuse-vertical-navigation-mode-side {
margin-left: calc( margin-left: calc(var(--fuse-vertical-navigation-thin-width) * -1);
var(--fuse-vertical-navigation-thin-width) * -1
);
} }
&.fuse-vertical-navigation-opened { &.fuse-vertical-navigation-opened {
@ -25,10 +25,9 @@ fuse-vertical-navigation {
/* Right positioned */ /* Right positioned */
&.fuse-vertical-navigation-position-right { &.fuse-vertical-navigation-position-right {
&.fuse-vertical-navigation-mode-side { &.fuse-vertical-navigation-mode-side {
margin-right: calc( margin-right: calc(var(--fuse-vertical-navigation-thin-width) * -1);
var(--fuse-vertical-navigation-thin-width) * -1
);
} }
&.fuse-vertical-navigation-opened { &.fuse-vertical-navigation-opened {
@ -43,8 +42,10 @@ fuse-vertical-navigation {
/* Wrapper */ /* Wrapper */
.fuse-vertical-navigation-wrapper { .fuse-vertical-navigation-wrapper {
/* Content */ /* Content */
.fuse-vertical-navigation-content { .fuse-vertical-navigation-content {
> fuse-vertical-navigation-aside-item, > fuse-vertical-navigation-aside-item,
> fuse-vertical-navigation-basic-item { > fuse-vertical-navigation-basic-item {
flex-direction: column; flex-direction: column;
@ -78,12 +79,13 @@ fuse-vertical-navigation {
} }
> fuse-vertical-navigation-collapsable-item { > fuse-vertical-navigation-collapsable-item {
display: none; display: none
} }
> fuse-vertical-navigation-group-item { > fuse-vertical-navigation-group-item {
> .fuse-vertical-navigation-item-wrapper { > .fuse-vertical-navigation-item-wrapper {
display: none; display: none
} }
} }
} }

View File

@ -1,4 +1,5 @@
<div class="fuse-vertical-navigation-wrapper"> <div class="fuse-vertical-navigation-wrapper">
<!-- Header --> <!-- Header -->
<div class="fuse-vertical-navigation-header"> <div class="fuse-vertical-navigation-header">
<ng-content select="[fuseVerticalNavigationHeader]"></ng-content> <ng-content select="[fuseVerticalNavigationHeader]"></ng-content>
@ -8,120 +9,114 @@
<div <div
class="fuse-vertical-navigation-content" class="fuse-vertical-navigation-content"
fuseScrollbar fuseScrollbar
[fuseScrollbarOptions]="{ [fuseScrollbarOptions]="{wheelPropagation: inner, suppressScrollX: true}"
wheelPropagation: inner, #navigationContent>
suppressScrollX: true,
}"
#navigationContent
>
<!-- Content header --> <!-- Content header -->
<div class="fuse-vertical-navigation-content-header"> <div class="fuse-vertical-navigation-content-header">
<ng-content <ng-content select="[fuseVerticalNavigationContentHeader]"></ng-content>
select="[fuseVerticalNavigationContentHeader]"
></ng-content>
</div> </div>
<!-- Items --> <!-- Items -->
@for (item of navigation; track trackByFn($index, item)) { <ng-container *ngFor="let item of navigation; trackBy: trackByFn">
<!-- Skip the hidden items --> <!-- Skip the hidden items -->
@if ((item.hidden && !item.hidden(item)) || !item.hidden) { <ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden">
<!-- Aside --> <!-- Aside -->
@if (item.type === 'aside') { <ng-container *ngIf="item.type === 'aside'">
<fuse-vertical-navigation-aside-item <fuse-vertical-navigation-aside-item
[item]="item" [item]="item"
[name]="name" [name]="name"
[activeItemId]="activeAsideItemId" [activeItemId]="activeAsideItemId"
[autoCollapse]="autoCollapse" [autoCollapse]="autoCollapse"
[skipChildren]="true" [skipChildren]="true"
(click)="toggleAside(item)" (click)="toggleAside(item)"></fuse-vertical-navigation-aside-item>
></fuse-vertical-navigation-aside-item> </ng-container>
}
<!-- Basic --> <!-- Basic -->
@if (item.type === 'basic') { <ng-container *ngIf="item.type === 'basic'">
<fuse-vertical-navigation-basic-item <fuse-vertical-navigation-basic-item
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-vertical-navigation-basic-item>
></fuse-vertical-navigation-basic-item> </ng-container>
}
<!-- Collapsable --> <!-- Collapsable -->
@if (item.type === 'collapsable') { <ng-container *ngIf="item.type === 'collapsable'">
<fuse-vertical-navigation-collapsable-item <fuse-vertical-navigation-collapsable-item
[item]="item" [item]="item"
[name]="name" [name]="name"
[autoCollapse]="autoCollapse" [autoCollapse]="autoCollapse"></fuse-vertical-navigation-collapsable-item>
></fuse-vertical-navigation-collapsable-item> </ng-container>
}
<!-- Divider --> <!-- Divider -->
@if (item.type === 'divider') { <ng-container *ngIf="item.type === 'divider'">
<fuse-vertical-navigation-divider-item <fuse-vertical-navigation-divider-item
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-vertical-navigation-divider-item>
></fuse-vertical-navigation-divider-item> </ng-container>
}
<!-- Group --> <!-- Group -->
@if (item.type === 'group') { <ng-container *ngIf="item.type === 'group'">
<fuse-vertical-navigation-group-item <fuse-vertical-navigation-group-item
[item]="item" [item]="item"
[name]="name" [name]="name"
[autoCollapse]="autoCollapse" [autoCollapse]="autoCollapse"></fuse-vertical-navigation-group-item>
></fuse-vertical-navigation-group-item> </ng-container>
}
<!-- Spacer --> <!-- Spacer -->
@if (item.type === 'spacer') { <ng-container *ngIf="item.type === 'spacer'">
<fuse-vertical-navigation-spacer-item <fuse-vertical-navigation-spacer-item
[item]="item" [item]="item"
[name]="name" [name]="name"></fuse-vertical-navigation-spacer-item>
></fuse-vertical-navigation-spacer-item> </ng-container>
}
} </ng-container>
}
</ng-container>
<!-- Content footer --> <!-- Content footer -->
<div class="fuse-vertical-navigation-content-footer"> <div class="fuse-vertical-navigation-content-footer">
<ng-content <ng-content select="[fuseVerticalNavigationContentFooter]"></ng-content>
select="[fuseVerticalNavigationContentFooter]"
></ng-content>
</div> </div>
</div> </div>
<!-- Footer --> <!-- Footer -->
<div class="fuse-vertical-navigation-footer"> <div class="fuse-vertical-navigation-footer">
<ng-content select="[fuseVerticalNavigationFooter]"></ng-content> <ng-content select="[fuseVerticalNavigationFooter]"></ng-content>
</div> </div>
</div> </div>
<!-- Aside --> <!-- Aside -->
@if (activeAsideItemId) { <ng-container *ngIf="activeAsideItemId">
<div <div
class="fuse-vertical-navigation-aside-wrapper" class="fuse-vertical-navigation-aside-wrapper"
fuseScrollbar fuseScrollbar
[fuseScrollbarOptions]="{ [fuseScrollbarOptions]="{wheelPropagation: false, suppressScrollX: true}"
wheelPropagation: false,
suppressScrollX: true,
}"
[@fadeInLeft]="position === 'left'" [@fadeInLeft]="position === 'left'"
[@fadeInRight]="position === 'right'" [@fadeInRight]="position === 'right'"
[@fadeOutLeft]="position === 'left'" [@fadeOutLeft]="position === 'left'"
[@fadeOutRight]="position === 'right'" [@fadeOutRight]="position === 'right'">
>
<!-- Items --> <!-- Items -->
@for (item of navigation; track trackByFn($index, item)) { <ng-container *ngFor="let item of navigation; trackBy: trackByFn">
<!-- Skip the hidden items --> <!-- Skip the hidden items -->
@if ((item.hidden && !item.hidden(item)) || !item.hidden) { <ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden">
<!-- Aside --> <!-- Aside -->
@if (item.type === 'aside' && item.id === activeAsideItemId) { <ng-container *ngIf="item.type === 'aside' && item.id === activeAsideItemId">
<fuse-vertical-navigation-aside-item <fuse-vertical-navigation-aside-item
[item]="item" [item]="item"
[name]="name" [name]="name"
[autoCollapse]="autoCollapse" [autoCollapse]="autoCollapse"></fuse-vertical-navigation-aside-item>
></fuse-vertical-navigation-aside-item> </ng-container>
}
} </ng-container>
}
</ng-container>
</div> </div>
} </ng-container>

View File

@ -1,43 +1,12 @@
import { import { animate, AnimationBuilder, AnimationPlayer, style } from '@angular/animations';
animate,
AnimationBuilder,
AnimationPlayer,
style,
} from '@angular/animations';
import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';
import { ScrollStrategy, ScrollStrategyOptions } from '@angular/cdk/overlay'; import { ScrollStrategy, ScrollStrategyOptions } from '@angular/cdk/overlay';
import { DOCUMENT } from '@angular/common'; import { DOCUMENT, NgFor, NgIf } from '@angular/common';
import { import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, HostBinding, HostListener, Inject, Input, OnChanges, OnDestroy, OnInit, Output, QueryList, Renderer2, SimpleChanges, ViewChild, ViewChildren, ViewEncapsulation } from '@angular/core';
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostBinding,
HostListener,
inject,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
QueryList,
Renderer2,
SimpleChanges,
ViewChild,
ViewChildren,
ViewEncapsulation,
} from '@angular/core';
import { NavigationEnd, Router } from '@angular/router'; import { NavigationEnd, Router } from '@angular/router';
import { fuseAnimations } from '@fuse/animations'; import { fuseAnimations } from '@fuse/animations';
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service'; import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
import { import { FuseNavigationItem, FuseVerticalNavigationAppearance, FuseVerticalNavigationMode, FuseVerticalNavigationPosition } from '@fuse/components/navigation/navigation.types';
FuseNavigationItem,
FuseVerticalNavigationAppearance,
FuseVerticalNavigationMode,
FuseVerticalNavigationPosition,
} from '@fuse/components/navigation/navigation.types';
import { FuseVerticalNavigationAsideItemComponent } from '@fuse/components/navigation/vertical/components/aside/aside.component'; import { FuseVerticalNavigationAsideItemComponent } from '@fuse/components/navigation/vertical/components/aside/aside.component';
import { FuseVerticalNavigationBasicItemComponent } from '@fuse/components/navigation/vertical/components/basic/basic.component'; import { FuseVerticalNavigationBasicItemComponent } from '@fuse/components/navigation/vertical/components/basic/basic.component';
import { FuseVerticalNavigationCollapsableItemComponent } from '@fuse/components/navigation/vertical/components/collapsable/collapsable.component'; import { FuseVerticalNavigationCollapsableItemComponent } from '@fuse/components/navigation/vertical/components/collapsable/collapsable.component';
@ -46,36 +15,20 @@ import { FuseVerticalNavigationGroupItemComponent } from '@fuse/components/navig
import { FuseVerticalNavigationSpacerItemComponent } from '@fuse/components/navigation/vertical/components/spacer/spacer.component'; import { FuseVerticalNavigationSpacerItemComponent } from '@fuse/components/navigation/vertical/components/spacer/spacer.component';
import { FuseScrollbarDirective } from '@fuse/directives/scrollbar/scrollbar.directive'; import { FuseScrollbarDirective } from '@fuse/directives/scrollbar/scrollbar.directive';
import { FuseUtilsService } from '@fuse/services/utils/utils.service'; import { FuseUtilsService } from '@fuse/services/utils/utils.service';
import { import { delay, filter, merge, ReplaySubject, Subject, Subscription, takeUntil } from 'rxjs';
delay,
filter,
merge,
ReplaySubject,
Subject,
Subscription,
takeUntil,
} from 'rxjs';
@Component({ @Component({
selector: 'fuse-vertical-navigation', selector : 'fuse-vertical-navigation',
templateUrl: './vertical.component.html', templateUrl : './vertical.component.html',
styleUrls: ['./vertical.component.scss'], styleUrls : ['./vertical.component.scss'],
animations: fuseAnimations, animations : fuseAnimations,
encapsulation: ViewEncapsulation.None, encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'fuseVerticalNavigation', exportAs : 'fuseVerticalNavigation',
imports: [ standalone : true,
FuseScrollbarDirective, imports : [FuseScrollbarDirective, NgFor, NgIf, FuseVerticalNavigationAsideItemComponent, FuseVerticalNavigationBasicItemComponent, FuseVerticalNavigationCollapsableItemComponent, FuseVerticalNavigationDividerItemComponent, FuseVerticalNavigationGroupItemComponent, FuseVerticalNavigationSpacerItemComponent],
FuseVerticalNavigationAsideItemComponent,
FuseVerticalNavigationBasicItemComponent,
FuseVerticalNavigationCollapsableItemComponent,
FuseVerticalNavigationDividerItemComponent,
FuseVerticalNavigationGroupItemComponent,
FuseVerticalNavigationSpacerItemComponent,
],
}) })
export class FuseVerticalNavigationComponent export class FuseVerticalNavigationComponent implements OnChanges, OnInit, AfterViewInit, OnDestroy
implements OnChanges, OnInit, AfterViewInit, OnDestroy
{ {
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
static ngAcceptInputType_inner: BooleanInput; static ngAcceptInputType_inner: BooleanInput;
@ -83,16 +36,6 @@ export class FuseVerticalNavigationComponent
static ngAcceptInputType_transparentOverlay: BooleanInput; static ngAcceptInputType_transparentOverlay: BooleanInput;
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
private _animationBuilder = inject(AnimationBuilder);
private _changeDetectorRef = inject(ChangeDetectorRef);
private _document = inject(DOCUMENT);
private _elementRef = inject(ElementRef);
private _renderer2 = inject(Renderer2);
private _router = inject(Router);
private _scrollStrategyOptions = inject(ScrollStrategyOptions);
private _fuseNavigationService = inject(FuseNavigationService);
private _fuseUtilsService = inject(FuseUtilsService);
@Input() appearance: FuseVerticalNavigationAppearance = 'default'; @Input() appearance: FuseVerticalNavigationAppearance = 'default';
@Input() autoCollapse: boolean = true; @Input() autoCollapse: boolean = true;
@Input() inner: boolean = false; @Input() inner: boolean = false;
@ -102,23 +45,15 @@ export class FuseVerticalNavigationComponent
@Input() opened: boolean = true; @Input() opened: boolean = true;
@Input() position: FuseVerticalNavigationPosition = 'left'; @Input() position: FuseVerticalNavigationPosition = 'left';
@Input() transparentOverlay: boolean = false; @Input() transparentOverlay: boolean = false;
@Output() @Output() readonly appearanceChanged: EventEmitter<FuseVerticalNavigationAppearance> = new EventEmitter<FuseVerticalNavigationAppearance>();
readonly appearanceChanged: EventEmitter<FuseVerticalNavigationAppearance> = @Output() readonly modeChanged: EventEmitter<FuseVerticalNavigationMode> = new EventEmitter<FuseVerticalNavigationMode>();
new EventEmitter<FuseVerticalNavigationAppearance>(); @Output() readonly openedChanged: EventEmitter<boolean> = new EventEmitter<boolean>();
@Output() readonly modeChanged: EventEmitter<FuseVerticalNavigationMode> = @Output() readonly positionChanged: EventEmitter<FuseVerticalNavigationPosition> = new EventEmitter<FuseVerticalNavigationPosition>();
new EventEmitter<FuseVerticalNavigationMode>();
@Output() readonly openedChanged: EventEmitter<boolean> =
new EventEmitter<boolean>();
@Output()
readonly positionChanged: EventEmitter<FuseVerticalNavigationPosition> =
new EventEmitter<FuseVerticalNavigationPosition>();
@ViewChild('navigationContent') private _navigationContentEl: ElementRef; @ViewChild('navigationContent') private _navigationContentEl: ElementRef;
activeAsideItemId: string | null = null; activeAsideItemId: string | null = null;
onCollapsableItemCollapsed: ReplaySubject<FuseNavigationItem> = onCollapsableItemCollapsed: ReplaySubject<FuseNavigationItem> = new ReplaySubject<FuseNavigationItem>(1);
new ReplaySubject<FuseNavigationItem>(1); onCollapsableItemExpanded: ReplaySubject<FuseNavigationItem> = new ReplaySubject<FuseNavigationItem>(1);
onCollapsableItemExpanded: ReplaySubject<FuseNavigationItem> =
new ReplaySubject<FuseNavigationItem>(1);
onRefreshed: ReplaySubject<boolean> = new ReplaySubject<boolean>(1); onRefreshed: ReplaySubject<boolean> = new ReplaySubject<boolean>(1);
private _animationsEnabled: boolean = false; private _animationsEnabled: boolean = false;
private _asideOverlay: HTMLElement; private _asideOverlay: HTMLElement;
@ -128,8 +63,7 @@ export class FuseVerticalNavigationComponent
private _mutationObserver: MutationObserver; private _mutationObserver: MutationObserver;
private _overlay: HTMLElement; private _overlay: HTMLElement;
private _player: AnimationPlayer; private _player: AnimationPlayer;
private _scrollStrategy: ScrollStrategy = private _scrollStrategy: ScrollStrategy = this._scrollStrategyOptions.block();
this._scrollStrategyOptions.block();
private _fuseScrollbarDirectives!: QueryList<FuseScrollbarDirective>; private _fuseScrollbarDirectives!: QueryList<FuseScrollbarDirective>;
private _fuseScrollbarDirectivesSubscription: Subscription; private _fuseScrollbarDirectivesSubscription: Subscription;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
@ -137,11 +71,24 @@ export class FuseVerticalNavigationComponent
/** /**
* Constructor * Constructor
*/ */
constructor() { constructor(
this._handleAsideOverlayClick = (): void => { private _animationBuilder: AnimationBuilder,
private _changeDetectorRef: ChangeDetectorRef,
@Inject(DOCUMENT) private _document: Document,
private _elementRef: ElementRef,
private _renderer2: Renderer2,
private _router: Router,
private _scrollStrategyOptions: ScrollStrategyOptions,
private _fuseNavigationService: FuseNavigationService,
private _fuseUtilsService: FuseUtilsService,
)
{
this._handleAsideOverlayClick = (): void =>
{
this.closeAside(); this.closeAside();
}; };
this._handleOverlayClick = (): void => { this._handleOverlayClick = (): void =>
{
this.close(); this.close();
}; };
} }
@ -153,20 +100,19 @@ export class FuseVerticalNavigationComponent
/** /**
* Host binding for component classes * Host binding for component classes
*/ */
@HostBinding('class') get classList(): any { @HostBinding('class') get classList(): any
{
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
return { return {
'fuse-vertical-navigation-animations-enabled': 'fuse-vertical-navigation-animations-enabled' : this._animationsEnabled,
this._animationsEnabled,
[`fuse-vertical-navigation-appearance-${this.appearance}`]: true, [`fuse-vertical-navigation-appearance-${this.appearance}`]: true,
'fuse-vertical-navigation-hover': this._hovered, 'fuse-vertical-navigation-hover' : this._hovered,
'fuse-vertical-navigation-inner': this.inner, 'fuse-vertical-navigation-inner' : this.inner,
'fuse-vertical-navigation-mode-over': this.mode === 'over', 'fuse-vertical-navigation-mode-over' : this.mode === 'over',
'fuse-vertical-navigation-mode-side': this.mode === 'side', 'fuse-vertical-navigation-mode-side' : this.mode === 'side',
'fuse-vertical-navigation-opened': this.opened, 'fuse-vertical-navigation-opened' : this.opened,
'fuse-vertical-navigation-position-left': this.position === 'left', 'fuse-vertical-navigation-position-left' : this.position === 'left',
'fuse-vertical-navigation-position-right': 'fuse-vertical-navigation-position-right' : this.position === 'right',
this.position === 'right',
}; };
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
} }
@ -174,9 +120,10 @@ export class FuseVerticalNavigationComponent
/** /**
* Host binding for component inline styles * Host binding for component inline styles
*/ */
@HostBinding('style') get styleList(): any { @HostBinding('style') get styleList(): any
{
return { return {
visibility: this.opened ? 'visible' : 'hidden', 'visibility': this.opened ? 'visible' : 'hidden',
}; };
} }
@ -184,34 +131,41 @@ export class FuseVerticalNavigationComponent
* Setter for fuseScrollbarDirectives * Setter for fuseScrollbarDirectives
*/ */
@ViewChildren(FuseScrollbarDirective) @ViewChildren(FuseScrollbarDirective)
set fuseScrollbarDirectives( set fuseScrollbarDirectives(fuseScrollbarDirectives: QueryList<FuseScrollbarDirective>)
fuseScrollbarDirectives: QueryList<FuseScrollbarDirective> {
) {
// Store the directives // Store the directives
this._fuseScrollbarDirectives = fuseScrollbarDirectives; this._fuseScrollbarDirectives = fuseScrollbarDirectives;
// Return if there are no directives // Return if there are no directives
if (fuseScrollbarDirectives.length === 0) { if ( fuseScrollbarDirectives.length === 0 )
{
return; return;
} }
// Unsubscribe the previous subscriptions // Unsubscribe the previous subscriptions
if (this._fuseScrollbarDirectivesSubscription) { if ( this._fuseScrollbarDirectivesSubscription )
{
this._fuseScrollbarDirectivesSubscription.unsubscribe(); this._fuseScrollbarDirectivesSubscription.unsubscribe();
} }
// Update the scrollbars on collapsable items' collapse/expand // Update the scrollbars on collapsable items' collapse/expand
this._fuseScrollbarDirectivesSubscription = merge( this._fuseScrollbarDirectivesSubscription =
this.onCollapsableItemCollapsed, merge(
this.onCollapsableItemExpanded this.onCollapsableItemCollapsed,
) this.onCollapsableItemExpanded,
.pipe(takeUntil(this._unsubscribeAll), delay(250)) )
.subscribe(() => { .pipe(
// Loop through the scrollbars and update them takeUntil(this._unsubscribeAll),
fuseScrollbarDirectives.forEach((fuseScrollbarDirective) => { delay(250),
fuseScrollbarDirective.update(); )
.subscribe(() =>
{
// Loop through the scrollbars and update them
fuseScrollbarDirectives.forEach((fuseScrollbarDirective) =>
{
fuseScrollbarDirective.update();
});
}); });
});
} }
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -224,7 +178,8 @@ export class FuseVerticalNavigationComponent
* @private * @private
*/ */
@HostListener('mouseenter') @HostListener('mouseenter')
private _onMouseenter(): void { private _onMouseenter(): void
{
// Enable the animations // Enable the animations
this._enableAnimations(); this._enableAnimations();
@ -238,7 +193,8 @@ export class FuseVerticalNavigationComponent
* @private * @private
*/ */
@HostListener('mouseleave') @HostListener('mouseleave')
private _onMouseleave(): void { private _onMouseleave(): void
{
// Enable the animations // Enable the animations
this._enableAnimations(); this._enableAnimations();
@ -255,21 +211,25 @@ export class FuseVerticalNavigationComponent
* *
* @param changes * @param changes
*/ */
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void
{
// Appearance // Appearance
if ('appearance' in changes) { if ( 'appearance' in changes )
{
// Execute the observable // Execute the observable
this.appearanceChanged.next(changes.appearance.currentValue); this.appearanceChanged.next(changes.appearance.currentValue);
} }
// Inner // Inner
if ('inner' in changes) { if ( 'inner' in changes )
{
// Coerce the value to a boolean // Coerce the value to a boolean
this.inner = coerceBooleanProperty(changes.inner.currentValue); this.inner = coerceBooleanProperty(changes.inner.currentValue);
} }
// Mode // Mode
if ('mode' in changes) { if ( 'mode' in changes )
{
// Get the previous and current values // Get the previous and current values
const currentMode = changes.mode.currentValue; const currentMode = changes.mode.currentValue;
const previousMode = changes.mode.previousValue; const previousMode = changes.mode.previousValue;
@ -278,18 +238,21 @@ export class FuseVerticalNavigationComponent
this._disableAnimations(); this._disableAnimations();
// If the mode changes: 'over -> side' // If the mode changes: 'over -> side'
if (previousMode === 'over' && currentMode === 'side') { if ( previousMode === 'over' && currentMode === 'side' )
{
// Hide the overlay // Hide the overlay
this._hideOverlay(); this._hideOverlay();
} }
// If the mode changes: 'side -> over' // If the mode changes: 'side -> over'
if (previousMode === 'side' && currentMode === 'over') { if ( previousMode === 'side' && currentMode === 'over' )
{
// Close the aside // Close the aside
this.closeAside(); this.closeAside();
// If the navigation is opened // If the navigation is opened
if (this.opened) { if ( this.opened )
{
// Show the overlay // Show the overlay
this._showOverlay(); this._showOverlay();
} }
@ -301,19 +264,22 @@ export class FuseVerticalNavigationComponent
// Enable the animations after a delay // Enable the animations after a delay
// The delay must be bigger than the current transition-duration // The delay must be bigger than the current transition-duration
// to make sure nothing will be animated while the mode changing // to make sure nothing will be animated while the mode changing
setTimeout(() => { setTimeout(() =>
{
this._enableAnimations(); this._enableAnimations();
}, 500); }, 500);
} }
// Navigation // Navigation
if ('navigation' in changes) { if ( 'navigation' in changes )
{
// Mark for check // Mark for check
this._changeDetectorRef.markForCheck(); this._changeDetectorRef.markForCheck();
} }
// Opened // Opened
if ('opened' in changes) { if ( 'opened' in changes )
{
// Coerce the value to a boolean // Coerce the value to a boolean
this.opened = coerceBooleanProperty(changes.opened.currentValue); this.opened = coerceBooleanProperty(changes.opened.currentValue);
@ -322,26 +288,28 @@ export class FuseVerticalNavigationComponent
} }
// Position // Position
if ('position' in changes) { if ( 'position' in changes )
{
// Execute the observable // Execute the observable
this.positionChanged.next(changes.position.currentValue); this.positionChanged.next(changes.position.currentValue);
} }
// Transparent overlay // Transparent overlay
if ('transparentOverlay' in changes) { if ( 'transparentOverlay' in changes )
{
// Coerce the value to a boolean // Coerce the value to a boolean
this.transparentOverlay = coerceBooleanProperty( this.transparentOverlay = coerceBooleanProperty(changes.transparentOverlay.currentValue);
changes.transparentOverlay.currentValue
);
} }
} }
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Make sure the name input is not an empty string // Make sure the name input is not an empty string
if (this.name === '') { if ( this.name === '' )
{
this.name = this._fuseUtilsService.randomId(); this.name = this._fuseUtilsService.randomId();
} }
@ -351,18 +319,21 @@ export class FuseVerticalNavigationComponent
// Subscribe to the 'NavigationEnd' event // Subscribe to the 'NavigationEnd' event
this._router.events this._router.events
.pipe( .pipe(
filter((event) => event instanceof NavigationEnd), filter(event => event instanceof NavigationEnd),
takeUntil(this._unsubscribeAll) takeUntil(this._unsubscribeAll),
) )
.subscribe(() => { .subscribe(() =>
{
// If the mode is 'over' and the navigation is opened... // If the mode is 'over' and the navigation is opened...
if (this.mode === 'over' && this.opened) { if ( this.mode === 'over' && this.opened )
{
// Close the navigation // Close the navigation
this.close(); this.close();
} }
// If the mode is 'side' and the aside is active... // If the mode is 'side' and the aside is active...
if (this.mode === 'side' && this.activeAsideItemId) { if ( this.mode === 'side' && this.activeAsideItemId )
{
// Close the aside // Close the aside
this.closeAside(); this.closeAside();
} }
@ -372,85 +343,74 @@ export class FuseVerticalNavigationComponent
/** /**
* After view init * After view init
*/ */
ngAfterViewInit(): void { ngAfterViewInit(): void
{
// Fix for Firefox. // Fix for Firefox.
// //
// Because 'position: sticky' doesn't work correctly inside a 'position: fixed' parent, // Because 'position: sticky' doesn't work correctly inside a 'position: fixed' parent,
// adding the '.cdk-global-scrollblock' to the html element breaks the navigation's position. // adding the '.cdk-global-scrollblock' to the html element breaks the navigation's position.
// This fixes the problem by reading the 'top' value from the html element and adding it as a // This fixes the problem by reading the 'top' value from the html element and adding it as a
// 'marginTop' to the navigation itself. // 'marginTop' to the navigation itself.
this._mutationObserver = new MutationObserver((mutations) => { this._mutationObserver = new MutationObserver((mutations) =>
mutations.forEach((mutation) => { {
mutations.forEach((mutation) =>
{
const mutationTarget = mutation.target as HTMLElement; const mutationTarget = mutation.target as HTMLElement;
if (mutation.attributeName === 'class') { if ( mutation.attributeName === 'class' )
if ( {
mutationTarget.classList.contains( if ( mutationTarget.classList.contains('cdk-global-scrollblock') )
'cdk-global-scrollblock' {
)
) {
const top = parseInt(mutationTarget.style.top, 10); const top = parseInt(mutationTarget.style.top, 10);
this._renderer2.setStyle( this._renderer2.setStyle(this._elementRef.nativeElement, 'margin-top', `${Math.abs(top)}px`);
this._elementRef.nativeElement, }
'margin-top', else
`${Math.abs(top)}px` {
); this._renderer2.setStyle(this._elementRef.nativeElement, 'margin-top', null);
} else {
this._renderer2.setStyle(
this._elementRef.nativeElement,
'margin-top',
null
);
} }
} }
}); });
}); });
this._mutationObserver.observe(this._document.documentElement, { this._mutationObserver.observe(this._document.documentElement, {
attributes: true, attributes : true,
attributeFilter: ['class'], attributeFilter: ['class'],
}); });
setTimeout(() => { setTimeout(() =>
{
// Return if 'navigation content' element does not exist // Return if 'navigation content' element does not exist
if (!this._navigationContentEl) { if ( !this._navigationContentEl )
{
return; return;
} }
// If 'navigation content' element doesn't have // If 'navigation content' element doesn't have
// perfect scrollbar activated on it... // perfect scrollbar activated on it...
if ( if ( !this._navigationContentEl.nativeElement.classList.contains('ps') )
!this._navigationContentEl.nativeElement.classList.contains( {
'ps'
)
) {
// Find the active item // Find the active item
const activeItem = const activeItem = this._navigationContentEl.nativeElement.querySelector('.fuse-vertical-navigation-item-active');
this._navigationContentEl.nativeElement.querySelector(
'.fuse-vertical-navigation-item-active'
);
// If the active item exists, scroll it into view // If the active item exists, scroll it into view
if (activeItem) { if ( activeItem )
{
activeItem.scrollIntoView(); activeItem.scrollIntoView();
} }
} }
// Otherwise // Otherwise
else { else
{
// Go through all the scrollbar directives // Go through all the scrollbar directives
this._fuseScrollbarDirectives.forEach( this._fuseScrollbarDirectives.forEach((fuseScrollbarDirective) =>
(fuseScrollbarDirective) => { {
// Skip if not enabled // Skip if not enabled
if (!fuseScrollbarDirective.isEnabled()) { if ( !fuseScrollbarDirective.isEnabled() )
return; {
} return;
// Scroll to the active element
fuseScrollbarDirective.scrollToElement(
'.fuse-vertical-navigation-item-active',
-120,
true
);
} }
);
// Scroll to the active element
fuseScrollbarDirective.scrollToElement('.fuse-vertical-navigation-item-active', -120, true);
});
} }
}); });
} }
@ -458,7 +418,8 @@ export class FuseVerticalNavigationComponent
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
// Disconnect the mutation observer // Disconnect the mutation observer
this._mutationObserver.disconnect(); this._mutationObserver.disconnect();
@ -481,7 +442,8 @@ export class FuseVerticalNavigationComponent
/** /**
* Refresh the component to apply the changes * Refresh the component to apply the changes
*/ */
refresh(): void { refresh(): void
{
// Mark for check // Mark for check
this._changeDetectorRef.markForCheck(); this._changeDetectorRef.markForCheck();
@ -492,9 +454,11 @@ export class FuseVerticalNavigationComponent
/** /**
* Open the navigation * Open the navigation
*/ */
open(): void { open(): void
{
// Return if the navigation is already open // Return if the navigation is already open
if (this.opened) { if ( this.opened )
{
return; return;
} }
@ -505,9 +469,11 @@ export class FuseVerticalNavigationComponent
/** /**
* Close the navigation * Close the navigation
*/ */
close(): void { close(): void
{
// Return if the navigation is already closed // Return if the navigation is already closed
if (!this.opened) { if ( !this.opened )
{
return; return;
} }
@ -521,11 +487,15 @@ export class FuseVerticalNavigationComponent
/** /**
* Toggle the navigation * Toggle the navigation
*/ */
toggle(): void { toggle(): void
{
// Toggle // Toggle
if (this.opened) { if ( this.opened )
{
this.close(); this.close();
} else { }
else
{
this.open(); this.open();
} }
} }
@ -535,9 +505,11 @@ export class FuseVerticalNavigationComponent
* *
* @param item * @param item
*/ */
openAside(item: FuseNavigationItem): void { openAside(item: FuseNavigationItem): void
{
// Return if the item is disabled // Return if the item is disabled
if (item.disabled || !item.id) { if ( item.disabled || !item.id )
{
return; return;
} }
@ -554,7 +526,8 @@ export class FuseVerticalNavigationComponent
/** /**
* Close the aside * Close the aside
*/ */
closeAside(): void { closeAside(): void
{
// Close // Close
this.activeAsideItemId = null; this.activeAsideItemId = null;
@ -570,11 +543,15 @@ export class FuseVerticalNavigationComponent
* *
* @param item * @param item
*/ */
toggleAside(item: FuseNavigationItem): void { toggleAside(item: FuseNavigationItem): void
{
// Toggle // Toggle
if (this.activeAsideItemId === item.id) { if ( this.activeAsideItemId === item.id )
{
this.closeAside(); this.closeAside();
} else { }
else
{
this.openAside(item); this.openAside(item);
} }
} }
@ -585,7 +562,8 @@ export class FuseVerticalNavigationComponent
* @param index * @param index
* @param item * @param item
*/ */
trackByFn(index: number, item: any): any { trackByFn(index: number, item: any): any
{
return item.id || index; return item.id || index;
} }
@ -598,9 +576,11 @@ export class FuseVerticalNavigationComponent
* *
* @private * @private
*/ */
private _enableAnimations(): void { private _enableAnimations(): void
{
// Return if the animations are already enabled // Return if the animations are already enabled
if (this._animationsEnabled) { if ( this._animationsEnabled )
{
return; return;
} }
@ -613,9 +593,11 @@ export class FuseVerticalNavigationComponent
* *
* @private * @private
*/ */
private _disableAnimations(): void { private _disableAnimations(): void
{
// Return if the animations are already disabled // Return if the animations are already disabled
if (!this._animationsEnabled) { if ( !this._animationsEnabled )
{
return; return;
} }
@ -628,9 +610,11 @@ export class FuseVerticalNavigationComponent
* *
* @private * @private
*/ */
private _showOverlay(): void { private _showOverlay(): void
{
// Return if there is already an overlay // Return if there is already an overlay
if (this._asideOverlay) { if ( this._asideOverlay )
{
return; return;
} }
@ -641,30 +625,21 @@ export class FuseVerticalNavigationComponent
this._overlay.classList.add('fuse-vertical-navigation-overlay'); this._overlay.classList.add('fuse-vertical-navigation-overlay');
// Add a class depending on the transparentOverlay option // Add a class depending on the transparentOverlay option
if (this.transparentOverlay) { if ( this.transparentOverlay )
this._overlay.classList.add( {
'fuse-vertical-navigation-overlay-transparent' this._overlay.classList.add('fuse-vertical-navigation-overlay-transparent');
);
} }
// Append the overlay to the parent of the navigation // Append the overlay to the parent of the navigation
this._renderer2.appendChild( this._renderer2.appendChild(this._elementRef.nativeElement.parentElement, this._overlay);
this._elementRef.nativeElement.parentElement,
this._overlay
);
// Enable block scroll strategy // Enable block scroll strategy
this._scrollStrategy.enable(); this._scrollStrategy.enable();
// Create the enter animation and attach it to the player // Create the enter animation and attach it to the player
this._player = this._animationBuilder this._player = this._animationBuilder.build([
.build([ animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({opacity: 1})),
animate( ]).create(this._overlay);
'300ms cubic-bezier(0.25, 0.8, 0.25, 1)',
style({ opacity: 1 })
),
])
.create(this._overlay);
// Play the animation // Play the animation
this._player.play(); this._player.play();
@ -678,33 +653,29 @@ export class FuseVerticalNavigationComponent
* *
* @private * @private
*/ */
private _hideOverlay(): void { private _hideOverlay(): void
if (!this._overlay) { {
if ( !this._overlay )
{
return; return;
} }
// Create the leave animation and attach it to the player // Create the leave animation and attach it to the player
this._player = this._animationBuilder this._player = this._animationBuilder.build([
.build([ animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({opacity: 0})),
animate( ]).create(this._overlay);
'300ms cubic-bezier(0.25, 0.8, 0.25, 1)',
style({ opacity: 0 })
),
])
.create(this._overlay);
// Play the animation // Play the animation
this._player.play(); this._player.play();
// Once the animation is done... // Once the animation is done...
this._player.onDone(() => { this._player.onDone(() =>
{
// If the overlay still exists... // If the overlay still exists...
if (this._overlay) { if ( this._overlay )
{
// Remove the event listener // Remove the event listener
this._overlay.removeEventListener( this._overlay.removeEventListener('click', this._handleOverlayClick);
'click',
this._handleOverlayClick
);
// Remove the overlay // Remove the overlay
this._overlay.parentNode.removeChild(this._overlay); this._overlay.parentNode.removeChild(this._overlay);
@ -721,9 +692,11 @@ export class FuseVerticalNavigationComponent
* *
* @private * @private
*/ */
private _showAsideOverlay(): void { private _showAsideOverlay(): void
{
// Return if there is already an overlay // Return if there is already an overlay
if (this._asideOverlay) { if ( this._asideOverlay )
{
return; return;
} }
@ -731,34 +704,23 @@ export class FuseVerticalNavigationComponent
this._asideOverlay = this._renderer2.createElement('div'); this._asideOverlay = this._renderer2.createElement('div');
// Add a class to the aside overlay element // Add a class to the aside overlay element
this._asideOverlay.classList.add( this._asideOverlay.classList.add('fuse-vertical-navigation-aside-overlay');
'fuse-vertical-navigation-aside-overlay'
);
// Append the aside overlay to the parent of the navigation // Append the aside overlay to the parent of the navigation
this._renderer2.appendChild( this._renderer2.appendChild(this._elementRef.nativeElement.parentElement, this._asideOverlay);
this._elementRef.nativeElement.parentElement,
this._asideOverlay
);
// Create the enter animation and attach it to the player // Create the enter animation and attach it to the player
this._player = this._animationBuilder this._player =
.build([ this._animationBuilder
animate( .build([
'300ms cubic-bezier(0.25, 0.8, 0.25, 1)', animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({opacity: 1})),
style({ opacity: 1 }) ]).create(this._asideOverlay);
),
])
.create(this._asideOverlay);
// Play the animation // Play the animation
this._player.play(); this._player.play();
// Add an event listener to the aside overlay // Add an event listener to the aside overlay
this._asideOverlay.addEventListener( this._asideOverlay.addEventListener('click', this._handleAsideOverlayClick);
'click',
this._handleAsideOverlayClick
);
} }
/** /**
@ -766,33 +728,31 @@ export class FuseVerticalNavigationComponent
* *
* @private * @private
*/ */
private _hideAsideOverlay(): void { private _hideAsideOverlay(): void
if (!this._asideOverlay) { {
if ( !this._asideOverlay )
{
return; return;
} }
// Create the leave animation and attach it to the player // Create the leave animation and attach it to the player
this._player = this._animationBuilder this._player =
.build([ this._animationBuilder
animate( .build([
'300ms cubic-bezier(0.25, 0.8, 0.25, 1)', animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({opacity: 0})),
style({ opacity: 0 }) ]).create(this._asideOverlay);
),
])
.create(this._asideOverlay);
// Play the animation // Play the animation
this._player.play(); this._player.play();
// Once the animation is done... // Once the animation is done...
this._player.onDone(() => { this._player.onDone(() =>
{
// If the aside overlay still exists... // If the aside overlay still exists...
if (this._asideOverlay) { if ( this._asideOverlay )
{
// Remove the event listener // Remove the event listener
this._asideOverlay.removeEventListener( this._asideOverlay.removeEventListener('click', this._handleAsideOverlayClick);
'click',
this._handleAsideOverlayClick
);
// Remove the aside overlay // Remove the aside overlay
this._asideOverlay.parentNode.removeChild(this._asideOverlay); this._asideOverlay.parentNode.removeChild(this._asideOverlay);
@ -807,7 +767,8 @@ export class FuseVerticalNavigationComponent
* @param open * @param open
* @private * @private
*/ */
private _toggleOpened(open: boolean): void { private _toggleOpened(open: boolean): void
{
// Set the opened // Set the opened
this.opened = open; this.opened = open;
@ -816,10 +777,14 @@ export class FuseVerticalNavigationComponent
// If the navigation opened, and the mode // If the navigation opened, and the mode
// is 'over', show the overlay // is 'over', show the overlay
if (this.mode === 'over') { if ( this.mode === 'over' )
if (this.opened) { {
if ( this.opened )
{
this._showOverlay(); this._showOverlay();
} else { }
else
{
this._hideOverlay(); this._hideOverlay();
} }
} }

View File

@ -1,24 +1,26 @@
import { import { Directive, ElementRef, OnDestroy, OnInit } from '@angular/core';
Directive,
ElementRef,
inject,
OnDestroy,
OnInit,
} from '@angular/core';
import { NavigationEnd, Router } from '@angular/router'; import { NavigationEnd, Router } from '@angular/router';
import { filter, Subject, takeUntil } from 'rxjs'; import { filter, Subject, takeUntil } from 'rxjs';
@Directive({ @Directive({
selector: '[fuseScrollReset]', selector : '[fuseScrollReset]',
exportAs: 'fuseScrollReset', exportAs : 'fuseScrollReset',
standalone: true, standalone: true,
}) })
export class FuseScrollResetDirective implements OnInit, OnDestroy { export class FuseScrollResetDirective implements OnInit, OnDestroy
private _elementRef = inject(ElementRef); {
private _router = inject(Router);
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(
private _elementRef: ElementRef,
private _router: Router,
)
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks // @ Lifecycle hooks
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -26,23 +28,24 @@ export class FuseScrollResetDirective implements OnInit, OnDestroy {
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Subscribe to NavigationEnd event // Subscribe to NavigationEnd event
this._router.events this._router.events.pipe(
.pipe( filter(event => event instanceof NavigationEnd),
filter((event) => event instanceof NavigationEnd), takeUntil(this._unsubscribeAll),
takeUntil(this._unsubscribeAll) ).subscribe(() =>
) {
.subscribe(() => { // Reset the element's scroll position to the top
// Reset the element's scroll position to the top this._elementRef.nativeElement.scrollTop = 0;
this._elementRef.nativeElement.scrollTop = 0; });
});
} }
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
this._unsubscribeAll.next(null); this._unsubscribeAll.next(null);
this._unsubscribeAll.complete(); this._unsubscribeAll.complete();

View File

@ -1,39 +1,26 @@
import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';
import { Platform } from '@angular/cdk/platform'; import { Platform } from '@angular/cdk/platform';
import { import { Directive, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
Directive, import { Router } from '@angular/router';
ElementRef, import { ScrollbarGeometry, ScrollbarPosition } from '@fuse/directives/scrollbar/scrollbar.types';
Input,
OnChanges,
OnDestroy,
OnInit,
SimpleChanges,
inject,
} from '@angular/core';
import {
ScrollbarGeometry,
ScrollbarPosition,
} from '@fuse/directives/scrollbar/scrollbar.types';
import { merge } from 'lodash-es'; import { merge } from 'lodash-es';
import PerfectScrollbar from 'perfect-scrollbar'; import PerfectScrollbar from 'perfect-scrollbar';
import { Subject, debounceTime, fromEvent, takeUntil } from 'rxjs'; import { debounceTime, fromEvent, Subject, takeUntil } from 'rxjs';
/** /**
* Wrapper directive for the Perfect Scrollbar: https://github.com/mdbootstrap/perfect-scrollbar * Wrapper directive for the Perfect Scrollbar: https://github.com/mdbootstrap/perfect-scrollbar
*/ */
@Directive({ @Directive({
selector: '[fuseScrollbar]', selector : '[fuseScrollbar]',
exportAs: 'fuseScrollbar', exportAs : 'fuseScrollbar',
standalone: true, standalone: true,
}) })
export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy { export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy
{
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
static ngAcceptInputType_fuseScrollbar: BooleanInput; static ngAcceptInputType_fuseScrollbar: BooleanInput;
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
private _elementRef = inject(ElementRef);
private _platform = inject(Platform);
@Input() fuseScrollbar: boolean = true; @Input() fuseScrollbar: boolean = true;
@Input() fuseScrollbarOptions: PerfectScrollbar.Options; @Input() fuseScrollbarOptions: PerfectScrollbar.Options;
@ -42,6 +29,17 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
private _ps: PerfectScrollbar; private _ps: PerfectScrollbar;
private _unsubscribeAll: Subject<any> = new Subject<any>(); private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(
private _elementRef: ElementRef,
private _platform: Platform,
private _router: Router,
)
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Accessors // @ Accessors
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -49,14 +47,16 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
/** /**
* Getter for _elementRef * Getter for _elementRef
*/ */
get elementRef(): ElementRef { get elementRef(): ElementRef
{
return this._elementRef; return this._elementRef;
} }
/** /**
* Getter for _ps * Getter for _ps
*/ */
get ps(): PerfectScrollbar | null { get ps(): PerfectScrollbar | null
{
return this._ps; return this._ps;
} }
@ -69,44 +69,46 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
* *
* @param changes * @param changes
*/ */
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void
{
// Enabled // Enabled
if ('fuseScrollbar' in changes) { if ( 'fuseScrollbar' in changes )
{
// Interpret empty string as 'true' // Interpret empty string as 'true'
this.fuseScrollbar = coerceBooleanProperty( this.fuseScrollbar = coerceBooleanProperty(changes.fuseScrollbar.currentValue);
changes.fuseScrollbar.currentValue
);
// If enabled, init the directive // If enabled, init the directive
if (this.fuseScrollbar) { if ( this.fuseScrollbar )
{
this._init(); this._init();
} }
// Otherwise destroy it // Otherwise destroy it
else { else
{
this._destroy(); this._destroy();
} }
} }
// Scrollbar options // Scrollbar options
if ('fuseScrollbarOptions' in changes) { if ( 'fuseScrollbarOptions' in changes )
{
// Merge the options // Merge the options
this._options = merge( this._options = merge({}, this._options, changes.fuseScrollbarOptions.currentValue);
{},
this._options,
changes.fuseScrollbarOptions.currentValue
);
// Return if not initialized // Return if not initialized
if (!this._ps) { if ( !this._ps )
{
return; return;
} }
// Destroy and re-init the PerfectScrollbar to update its options // Destroy and re-init the PerfectScrollbar to update its options
setTimeout(() => { setTimeout(() =>
{
this._destroy(); this._destroy();
}); });
setTimeout(() => { setTimeout(() =>
{
this._init(); this._init();
}); });
} }
@ -115,11 +117,16 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
/** /**
* On init * On init
*/ */
ngOnInit(): void { ngOnInit(): void
{
// Subscribe to window resize event // Subscribe to window resize event
fromEvent(window, 'resize') fromEvent(window, 'resize')
.pipe(takeUntil(this._unsubscribeAll), debounceTime(150)) .pipe(
.subscribe(() => { takeUntil(this._unsubscribeAll),
debounceTime(150),
)
.subscribe(() =>
{
// Update the PerfectScrollbar // Update the PerfectScrollbar
this.update(); this.update();
}); });
@ -128,7 +135,8 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
/** /**
* On destroy * On destroy
*/ */
ngOnDestroy(): void { ngOnDestroy(): void
{
this._destroy(); this._destroy();
// Unsubscribe from all subscriptions // Unsubscribe from all subscriptions
@ -143,16 +151,19 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
/** /**
* Is enabled * Is enabled
*/ */
isEnabled(): boolean { isEnabled(): boolean
{
return this.fuseScrollbar; return this.fuseScrollbar;
} }
/** /**
* Update the scrollbar * Update the scrollbar
*/ */
update(): void { update(): void
{
// Return if not initialized // Return if not initialized
if (!this._ps) { if ( !this._ps )
{
return; return;
} }
@ -163,7 +174,8 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
/** /**
* Destroy the scrollbar * Destroy the scrollbar
*/ */
destroy(): void { destroy(): void
{
this.ngOnDestroy(); this.ngOnDestroy();
} }
@ -172,13 +184,13 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
* *
* @param prefix * @param prefix
*/ */
geometry(prefix: string = 'scroll'): ScrollbarGeometry { geometry(prefix: string = 'scroll'): ScrollbarGeometry
{
return new ScrollbarGeometry( return new ScrollbarGeometry(
this._elementRef.nativeElement[prefix + 'Left'], this._elementRef.nativeElement[prefix + 'Left'],
this._elementRef.nativeElement[prefix + 'Top'], this._elementRef.nativeElement[prefix + 'Top'],
this._elementRef.nativeElement[prefix + 'Width'], this._elementRef.nativeElement[prefix + 'Width'],
this._elementRef.nativeElement[prefix + 'Height'] this._elementRef.nativeElement[prefix + 'Height']);
);
} }
/** /**
@ -186,18 +198,22 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
* *
* @param absolute * @param absolute
*/ */
position(absolute: boolean = false): ScrollbarPosition { position(absolute: boolean = false): ScrollbarPosition
{
let scrollbarPosition; let scrollbarPosition;
if (!absolute && this._ps) { if ( !absolute && this._ps )
{
scrollbarPosition = new ScrollbarPosition( scrollbarPosition = new ScrollbarPosition(
this._ps.reach.x || 0, this._ps.reach.x || 0,
this._ps.reach.y || 0 this._ps.reach.y || 0,
); );
} else { }
else
{
scrollbarPosition = new ScrollbarPosition( scrollbarPosition = new ScrollbarPosition(
this._elementRef.nativeElement.scrollLeft, this._elementRef.nativeElement.scrollLeft,
this._elementRef.nativeElement.scrollTop this._elementRef.nativeElement.scrollTop,
); );
} }
@ -211,15 +227,21 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
* @param y * @param y
* @param speed * @param speed
*/ */
scrollTo(x: number, y?: number, speed?: number): void { scrollTo(x: number, y?: number, speed?: number): void
if (y == null && speed == null) { {
if ( y == null && speed == null )
{
this.animateScrolling('scrollTop', x, speed); this.animateScrolling('scrollTop', x, speed);
} else { }
if (x != null) { else
{
if ( x != null )
{
this.animateScrolling('scrollLeft', x, speed); this.animateScrolling('scrollLeft', x, speed);
} }
if (y != null) { if ( y != null )
{
this.animateScrolling('scrollTop', y, speed); this.animateScrolling('scrollTop', y, speed);
} }
} }
@ -231,7 +253,8 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
* @param x * @param x
* @param speed * @param speed
*/ */
scrollToX(x: number, speed?: number): void { scrollToX(x: number, speed?: number): void
{
this.animateScrolling('scrollLeft', x, speed); this.animateScrolling('scrollLeft', x, speed);
} }
@ -241,7 +264,8 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
* @param y * @param y
* @param speed * @param speed
*/ */
scrollToY(y: number, speed?: number): void { scrollToY(y: number, speed?: number): void
{
this.animateScrolling('scrollTop', y, speed); this.animateScrolling('scrollTop', y, speed);
} }
@ -251,7 +275,8 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
* @param offset * @param offset
* @param speed * @param speed
*/ */
scrollToTop(offset: number = 0, speed?: number): void { scrollToTop(offset: number = 0, speed?: number): void
{
this.animateScrolling('scrollTop', offset, speed); this.animateScrolling('scrollTop', offset, speed);
} }
@ -261,10 +286,9 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
* @param offset * @param offset
* @param speed * @param speed
*/ */
scrollToBottom(offset: number = 0, speed?: number): void { scrollToBottom(offset: number = 0, speed?: number): void
const top = {
this._elementRef.nativeElement.scrollHeight - const top = this._elementRef.nativeElement.scrollHeight - this._elementRef.nativeElement.clientHeight;
this._elementRef.nativeElement.clientHeight;
this.animateScrolling('scrollTop', top - offset, speed); this.animateScrolling('scrollTop', top - offset, speed);
} }
@ -274,7 +298,8 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
* @param offset * @param offset
* @param speed * @param speed
*/ */
scrollToLeft(offset: number = 0, speed?: number): void { scrollToLeft(offset: number = 0, speed?: number): void
{
this.animateScrolling('scrollLeft', offset, speed); this.animateScrolling('scrollLeft', offset, speed);
} }
@ -284,10 +309,9 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
* @param offset * @param offset
* @param speed * @param speed
*/ */
scrollToRight(offset: number = 0, speed?: number): void { scrollToRight(offset: number = 0, speed?: number): void
const left = {
this._elementRef.nativeElement.scrollWidth - const left = this._elementRef.nativeElement.scrollWidth - this._elementRef.nativeElement.clientWidth;
this._elementRef.nativeElement.clientWidth;
this.animateScrolling('scrollLeft', left - offset, speed); this.animateScrolling('scrollLeft', left - offset, speed);
} }
@ -299,27 +323,22 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
* @param ignoreVisible If true, scrollToElement won't happen if element is already inside the current viewport * @param ignoreVisible If true, scrollToElement won't happen if element is already inside the current viewport
* @param speed * @param speed
*/ */
scrollToElement( scrollToElement(qs: string, offset: number = 0, ignoreVisible: boolean = false, speed?: number): void
qs: string, {
offset: number = 0,
ignoreVisible: boolean = false,
speed?: number
): void {
const element = this._elementRef.nativeElement.querySelector(qs); const element = this._elementRef.nativeElement.querySelector(qs);
if (!element) { if ( !element )
{
return; return;
} }
const elementPos = element.getBoundingClientRect(); const elementPos = element.getBoundingClientRect();
const scrollerPos = const scrollerPos = this._elementRef.nativeElement.getBoundingClientRect();
this._elementRef.nativeElement.getBoundingClientRect();
if (this._elementRef.nativeElement.classList.contains('ps--active-x')) { if ( this._elementRef.nativeElement.classList.contains('ps--active-x') )
if ( {
ignoreVisible && if ( ignoreVisible && elementPos.right <= (scrollerPos.right - Math.abs(offset)) )
elementPos.right <= scrollerPos.right - Math.abs(offset) {
) {
return; return;
} }
@ -329,11 +348,10 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
this.animateScrolling('scrollLeft', position + offset, speed); this.animateScrolling('scrollLeft', position + offset, speed);
} }
if (this._elementRef.nativeElement.classList.contains('ps--active-y')) { if ( this._elementRef.nativeElement.classList.contains('ps--active-y') )
if ( {
ignoreVisible && if ( ignoreVisible && elementPos.bottom <= (scrollerPos.bottom - Math.abs(offset)) )
elementPos.bottom <= scrollerPos.bottom - Math.abs(offset) {
) {
return; return;
} }
@ -351,15 +369,20 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
* @param value * @param value
* @param speed * @param speed
*/ */
animateScrolling(target: string, value: number, speed?: number): void { animateScrolling(target: string, value: number, speed?: number): void
if (this._animation) { {
if ( this._animation )
{
window.cancelAnimationFrame(this._animation); window.cancelAnimationFrame(this._animation);
this._animation = null; this._animation = null;
} }
if (!speed || typeof window === 'undefined') { if ( !speed || typeof window === 'undefined' )
{
this._elementRef.nativeElement[target] = value; this._elementRef.nativeElement[target] = value;
} else if (value !== this._elementRef.nativeElement[target]) { }
else if ( value !== this._elementRef.nativeElement[target] )
{
let newValue = 0; let newValue = 0;
let scrollCount = 0; let scrollCount = 0;
@ -368,18 +391,20 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
const cosParameter = (oldValue - value) / 2; const cosParameter = (oldValue - value) / 2;
const step = (newTimestamp: number): void => { const step = (newTimestamp: number): void =>
scrollCount += {
Math.PI / (speed / (newTimestamp - oldTimestamp)); scrollCount += Math.PI / (speed / (newTimestamp - oldTimestamp));
newValue = Math.round( newValue = Math.round(value + cosParameter + cosParameter * Math.cos(scrollCount));
value + cosParameter + cosParameter * Math.cos(scrollCount)
);
// Only continue animation if scroll position has not changed // Only continue animation if scroll position has not changed
if (this._elementRef.nativeElement[target] === oldValue) { if ( this._elementRef.nativeElement[target] === oldValue )
if (scrollCount >= Math.PI) { {
if ( scrollCount >= Math.PI )
{
this.animateScrolling(target, value, 0); this.animateScrolling(target, value, 0);
} else { }
else
{
this._elementRef.nativeElement[target] = newValue; this._elementRef.nativeElement[target] = newValue;
// On a zoomed out page the resulting offset may differ // On a zoomed out page the resulting offset may differ
@ -404,26 +429,23 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
* *
* @private * @private
*/ */
private _init(): void { private _init(): void
{
// Return if already initialized // Return if already initialized
if (this._ps) { if ( this._ps )
{
return; return;
} }
// Return if on mobile or not on browser // Return if on mobile or not on browser
if ( if ( this._platform.ANDROID || this._platform.IOS || !this._platform.isBrowser )
this._platform.ANDROID || {
this._platform.IOS ||
!this._platform.isBrowser
) {
this.fuseScrollbar = false; this.fuseScrollbar = false;
return; return;
} }
// Initialize the PerfectScrollbar // Initialize the PerfectScrollbar
this._ps = new PerfectScrollbar(this._elementRef.nativeElement, { this._ps = new PerfectScrollbar(this._elementRef.nativeElement, {...this._options});
...this._options,
});
} }
/** /**
@ -431,9 +453,11 @@ export class FuseScrollbarDirective implements OnChanges, OnInit, OnDestroy {
* *
* @private * @private
*/ */
private _destroy(): void { private _destroy(): void
{
// Return if not initialized // Return if not initialized
if (!this._ps) { if ( !this._ps )
{
return; return;
} }

View File

@ -1,11 +1,13 @@
export class ScrollbarGeometry { export class ScrollbarGeometry
{
public x: number; public x: number;
public y: number; public y: number;
public w: number; public w: number;
public h: number; public h: number;
constructor(x: number, y: number, w: number, h: number) { constructor(x: number, y: number, w: number, h: number)
{
this.x = x; this.x = x;
this.y = y; this.y = y;
this.w = w; this.w = w;
@ -13,11 +15,13 @@ export class ScrollbarGeometry {
} }
} }
export class ScrollbarPosition { export class ScrollbarPosition
{
public x: number | 'start' | 'end'; public x: number | 'start' | 'end';
public y: number | 'start' | 'end'; public y: number | 'start' | 'end';
constructor(x: number | 'start' | 'end', y: number | 'start' | 'end') { constructor(x: number | 'start' | 'end', y: number | 'start' | 'end')
{
this.x = x; this.x = x;
this.y = y; this.y = y;
} }

View File

@ -1,26 +1,13 @@
import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { import { APP_INITIALIZER, ENVIRONMENT_INITIALIZER, EnvironmentProviders, importProvidersFrom, inject, Provider } from '@angular/core';
EnvironmentProviders,
Provider,
importProvidersFrom,
inject,
provideAppInitializer,
provideEnvironmentInitializer,
} from '@angular/core';
import { MATERIAL_SANITY_CHECKS } from '@angular/material/core'; import { MATERIAL_SANITY_CHECKS } from '@angular/material/core';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field'; import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
import { import { FUSE_MOCK_API_DEFAULT_DELAY, mockApiInterceptor } from '@fuse/lib/mock-api';
FUSE_MOCK_API_DEFAULT_DELAY,
mockApiInterceptor,
} from '@fuse/lib/mock-api';
import { FuseConfig } from '@fuse/services/config'; import { FuseConfig } from '@fuse/services/config';
import { FUSE_CONFIG } from '@fuse/services/config/config.constants'; import { FUSE_CONFIG } from '@fuse/services/config/config.constants';
import { FuseConfirmationService } from '@fuse/services/confirmation'; import { FuseConfirmationService } from '@fuse/services/confirmation';
import { import { fuseLoadingInterceptor, FuseLoadingService } from '@fuse/services/loading';
FuseLoadingService,
fuseLoadingInterceptor,
} from '@fuse/services/loading';
import { FuseMediaWatcherService } from '@fuse/services/media-watcher'; import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
import { FusePlatformService } from '@fuse/services/platform'; import { FusePlatformService } from '@fuse/services/platform';
import { FuseSplashScreenService } from '@fuse/services/splash-screen'; import { FuseSplashScreenService } from '@fuse/services/splash-screen';
@ -29,63 +16,90 @@ import { FuseUtilsService } from '@fuse/services/utils';
export type FuseProviderConfig = { export type FuseProviderConfig = {
mockApi?: { mockApi?: {
delay?: number; delay?: number;
service?: any; services?: any[];
}; },
fuse?: FuseConfig; fuse?: FuseConfig
}; }
/** /**
* Fuse provider * Fuse provider
*/ */
export const provideFuse = ( export const provideFuse = (config: FuseProviderConfig): Array<Provider | EnvironmentProviders> =>
config: FuseProviderConfig {
): Array<Provider | EnvironmentProviders> => {
// Base providers // Base providers
const providers: Array<Provider | EnvironmentProviders> = [ const providers: Array<Provider | EnvironmentProviders> = [
{ {
// Disable 'theme' sanity check // Disable 'theme' sanity check
provide: MATERIAL_SANITY_CHECKS, provide : MATERIAL_SANITY_CHECKS,
useValue: { useValue: {
doctype: true, doctype: true,
theme: false, theme : false,
version: true, version: true,
}, },
}, },
{ {
// Use the 'fill' appearance on Angular Material form fields by default // Use the 'fill' appearance on Angular Material form fields by default
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, provide : MAT_FORM_FIELD_DEFAULT_OPTIONS,
useValue: { useValue: {
appearance: 'fill', appearance: 'fill',
}, },
}, },
{ {
provide: FUSE_MOCK_API_DEFAULT_DELAY, provide : FUSE_MOCK_API_DEFAULT_DELAY,
useValue: config?.mockApi?.delay ?? 0, useValue: config?.mockApi?.delay ?? 0,
}, },
{ {
provide: FUSE_CONFIG, provide : FUSE_CONFIG,
useValue: config?.fuse ?? {}, useValue: config?.fuse ?? {},
}, },
importProvidersFrom(MatDialogModule), importProvidersFrom(MatDialogModule),
provideEnvironmentInitializer(() => inject(FuseConfirmationService)), {
provide : ENVIRONMENT_INITIALIZER,
useValue: () => inject(FuseConfirmationService),
multi : true,
},
provideHttpClient(withInterceptors([fuseLoadingInterceptor])), provideHttpClient(withInterceptors([fuseLoadingInterceptor])),
provideEnvironmentInitializer(() => inject(FuseLoadingService)), {
provide : ENVIRONMENT_INITIALIZER,
useValue: () => inject(FuseLoadingService),
multi : true,
},
provideEnvironmentInitializer(() => inject(FuseMediaWatcherService)), {
provideEnvironmentInitializer(() => inject(FusePlatformService)), provide : ENVIRONMENT_INITIALIZER,
provideEnvironmentInitializer(() => inject(FuseSplashScreenService)), useValue: () => inject(FuseMediaWatcherService),
provideEnvironmentInitializer(() => inject(FuseUtilsService)), multi : true,
},
{
provide : ENVIRONMENT_INITIALIZER,
useValue: () => inject(FusePlatformService),
multi : true,
},
{
provide : ENVIRONMENT_INITIALIZER,
useValue: () => inject(FuseSplashScreenService),
multi : true,
},
{
provide : ENVIRONMENT_INITIALIZER,
useValue: () => inject(FuseUtilsService),
multi : true,
},
]; ];
// Mock Api services // Mock Api services
if (config?.mockApi?.service) { if ( config?.mockApi?.services )
{
providers.push( providers.push(
provideHttpClient(withInterceptors([mockApiInterceptor])), provideHttpClient(withInterceptors([mockApiInterceptor])),
provideAppInitializer(() => { {
const mockApiService = inject(config.mockApi.service); provide : APP_INITIALIZER,
}) deps : [...config.mockApi.services],
useFactory: () => (): any => null,
multi : true,
},
); );
} }

View File

@ -1,5 +1,3 @@
import { InjectionToken } from '@angular/core'; import { InjectionToken } from '@angular/core';
export const FUSE_MOCK_API_DEFAULT_DELAY = new InjectionToken<number>( export const FUSE_MOCK_API_DEFAULT_DELAY = new InjectionToken<number>('FUSE_MOCK_API_DEFAULT_DELAY');
'FUSE_MOCK_API_DEFAULT_DELAY'
);

View File

@ -1,30 +1,23 @@
import { import { HttpErrorResponse, HttpEvent, HttpHandlerFn, HttpRequest, HttpResponse } from '@angular/common/http';
HttpErrorResponse,
HttpEvent,
HttpHandlerFn,
HttpRequest,
HttpResponse,
} from '@angular/common/http';
import { inject } from '@angular/core'; import { inject } from '@angular/core';
import { FUSE_MOCK_API_DEFAULT_DELAY } from '@fuse/lib/mock-api/mock-api.constants'; import { FUSE_MOCK_API_DEFAULT_DELAY } from '@fuse/lib/mock-api/mock-api.constants';
import { FuseMockApiService } from '@fuse/lib/mock-api/mock-api.service'; import { FuseMockApiService } from '@fuse/lib/mock-api/mock-api.service';
import { Observable, delay, of, switchMap, throwError } from 'rxjs'; import { delay, Observable, of, switchMap, throwError } from 'rxjs';
export const mockApiInterceptor = ( export const mockApiInterceptor = (request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> =>
request: HttpRequest<unknown>, {
next: HttpHandlerFn
): Observable<HttpEvent<unknown>> => {
const defaultDelay = inject(FUSE_MOCK_API_DEFAULT_DELAY); const defaultDelay = inject(FUSE_MOCK_API_DEFAULT_DELAY);
const fuseMockApiService = inject(FuseMockApiService); const fuseMockApiService = inject(FuseMockApiService);
// Try to get the request handler // Try to get the request handler
const { handler, urlParams } = fuseMockApiService.findHandler( const {
request.method.toUpperCase(), handler,
request.url urlParams,
); } = fuseMockApiService.findHandler(request.method.toUpperCase(), request.url);
// Pass through if the request handler does not exist // Pass through if the request handler does not exist
if (!handler) { if ( !handler )
{
return next(request); return next(request);
} }
@ -37,13 +30,15 @@ export const mockApiInterceptor = (
// Subscribe to the response function observable // Subscribe to the response function observable
return handler.response.pipe( return handler.response.pipe(
delay(handler.delay ?? defaultDelay ?? 0), delay(handler.delay ?? defaultDelay ?? 0),
switchMap((response) => { switchMap((response) =>
{
// If there is no response data, // If there is no response data,
// throw an error response // throw an error response
if (!response) { if ( !response )
{
response = new HttpErrorResponse({ response = new HttpErrorResponse({
error: 'NOT FOUND', error : 'NOT FOUND',
status: 404, status : 404,
statusText: 'NOT FOUND', statusText: 'NOT FOUND',
}); });
@ -53,15 +48,16 @@ export const mockApiInterceptor = (
// Parse the response data // Parse the response data
const data = { const data = {
status: response[0], status: response[0],
body: response[1], body : response[1],
}; };
// If the status code is in between 200 and 300, // If the status code is in between 200 and 300,
// return a success response // return a success response
if (data.status >= 200 && data.status < 300) { if ( data.status >= 200 && data.status < 300 )
{
response = new HttpResponse({ response = new HttpResponse({
body: data.body, body : data.body,
status: data.status, status : data.status,
statusText: 'OK', statusText: 'OK',
}); });
@ -71,12 +67,11 @@ export const mockApiInterceptor = (
// For other status codes, // For other status codes,
// throw an error response // throw an error response
response = new HttpErrorResponse({ response = new HttpErrorResponse({
error: data.body.error, error : data.body.error,
status: data.status, status : data.status,
statusText: 'ERROR', statusText: 'ERROR',
}); });
return throwError(response); return throwError(response);
}) }));
);
}; };

View File

@ -2,7 +2,8 @@ import { HttpRequest } from '@angular/common/http';
import { FuseMockApiReplyCallback } from '@fuse/lib/mock-api/mock-api.types'; import { FuseMockApiReplyCallback } from '@fuse/lib/mock-api/mock-api.types';
import { Observable, of, take, throwError } from 'rxjs'; import { Observable, of, take, throwError } from 'rxjs';
export class FuseMockApiHandler { export class FuseMockApiHandler
{
request!: HttpRequest<any>; request!: HttpRequest<any>;
urlParams!: { [key: string]: string }; urlParams!: { [key: string]: string };
@ -16,8 +17,10 @@ export class FuseMockApiHandler {
*/ */
constructor( constructor(
public url: string, public url: string,
public delay?: number public delay?: number,
) {} )
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Accessors // @ Accessors
@ -26,19 +29,23 @@ export class FuseMockApiHandler {
/** /**
* Getter for response callback * Getter for response callback
*/ */
get response(): Observable<any> { get response(): Observable<any>
{
// If the execution limit has been reached, throw an error // If the execution limit has been reached, throw an error
if (this._replyCount > 0 && this._replyCount <= this._replied) { if ( this._replyCount > 0 && this._replyCount <= this._replied )
{
return throwError('Execution limit has been reached!'); return throwError('Execution limit has been reached!');
} }
// If the response callback has not been set, throw an error // If the response callback has not been set, throw an error
if (!this._reply) { if ( !this._reply )
{
return throwError('Response callback function does not exist!'); return throwError('Response callback function does not exist!');
} }
// If the request has not been set, throw an error // If the request has not been set, throw an error
if (!this.request) { if ( !this.request )
{
return throwError('Request does not exist!'); return throwError('Request does not exist!');
} }
@ -47,12 +54,13 @@ export class FuseMockApiHandler {
// Execute the reply callback // Execute the reply callback
const replyResult = this._reply({ const replyResult = this._reply({
request: this.request, request : this.request,
urlParams: this.urlParams, urlParams: this.urlParams,
}); });
// If the result of the reply callback is an observable... // If the result of the reply callback is an observable...
if (replyResult instanceof Observable) { if ( replyResult instanceof Observable )
{
// Return the result as it is // Return the result as it is
return replyResult.pipe(take(1)); return replyResult.pipe(take(1));
} }
@ -70,7 +78,8 @@ export class FuseMockApiHandler {
* *
* @param callback * @param callback
*/ */
reply(callback: FuseMockApiReplyCallback): void { reply(callback: FuseMockApiReplyCallback): void
{
// Store the reply // Store the reply
this._reply = callback; this._reply = callback;
} }
@ -80,8 +89,11 @@ export class FuseMockApiHandler {
* *
* @param count * @param count
*/ */
replyCount(count: number): void { replyCount(count: number): void
{
// Store the reply count // Store the reply count
this._replyCount = count; this._replyCount = count;
} }
} }

View File

@ -3,19 +3,27 @@ import { FuseMockApiHandler } from '@fuse/lib/mock-api/mock-api.request-handler'
import { FuseMockApiMethods } from '@fuse/lib/mock-api/mock-api.types'; import { FuseMockApiMethods } from '@fuse/lib/mock-api/mock-api.types';
import { compact, fromPairs } from 'lodash-es'; import { compact, fromPairs } from 'lodash-es';
@Injectable({ providedIn: 'root' }) @Injectable({providedIn: 'root'})
export class FuseMockApiService { export class FuseMockApiService
{
private _handlers: { [key: string]: Map<string, FuseMockApiHandler> } = { private _handlers: { [key: string]: Map<string, FuseMockApiHandler> } = {
get: new Map<string, FuseMockApiHandler>(), 'get' : new Map<string, FuseMockApiHandler>(),
post: new Map<string, FuseMockApiHandler>(), 'post' : new Map<string, FuseMockApiHandler>(),
patch: new Map<string, FuseMockApiHandler>(), 'patch' : new Map<string, FuseMockApiHandler>(),
delete: new Map<string, FuseMockApiHandler>(), 'delete' : new Map<string, FuseMockApiHandler>(),
put: new Map<string, FuseMockApiHandler>(), 'put' : new Map<string, FuseMockApiHandler>(),
head: new Map<string, FuseMockApiHandler>(), 'head' : new Map<string, FuseMockApiHandler>(),
jsonp: new Map<string, FuseMockApiHandler>(), 'jsonp' : new Map<string, FuseMockApiHandler>(),
options: new Map<string, FuseMockApiHandler>(), 'options': new Map<string, FuseMockApiHandler>(),
}; };
/**
* Constructor
*/
constructor()
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Public methods // @ Public methods
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -27,19 +35,11 @@ export class FuseMockApiService {
* @param method * @param method
* @param url * @param url
*/ */
findHandler( findHandler(method: string, url: string): { handler: FuseMockApiHandler | undefined; urlParams: { [key: string]: string } }
method: string, {
url: string
): {
handler: FuseMockApiHandler | undefined;
urlParams: { [key: string]: string };
} {
// Prepare the return object // Prepare the return object
const matchingHandler: { const matchingHandler: { handler: FuseMockApiHandler | undefined; urlParams: { [key: string]: string } } = {
handler: FuseMockApiHandler | undefined; handler : undefined,
urlParams: { [key: string]: string };
} = {
handler: undefined,
urlParams: {}, urlParams: {},
}; };
@ -50,9 +50,11 @@ export class FuseMockApiService {
const handlers = this._handlers[method.toLowerCase()]; const handlers = this._handlers[method.toLowerCase()];
// Iterate through the handlers // Iterate through the handlers
handlers.forEach((handler, handlerUrl) => { handlers.forEach((handler, handlerUrl) =>
{
// Skip if there is already a matching handler // Skip if there is already a matching handler
if (matchingHandler.handler) { if ( matchingHandler.handler )
{
return; return;
} }
@ -60,32 +62,24 @@ export class FuseMockApiService {
const handlerUrlParts = handlerUrl.split('/'); const handlerUrlParts = handlerUrl.split('/');
// Skip if the lengths of the urls we are comparing are not the same // Skip if the lengths of the urls we are comparing are not the same
if (urlParts.length !== handlerUrlParts.length) { if ( urlParts.length !== handlerUrlParts.length )
{
return; return;
} }
// Compare // Compare
const matches = handlerUrlParts.every( const matches = handlerUrlParts.every((handlerUrlPart, index) => handlerUrlPart === urlParts[index] || handlerUrlPart.startsWith(':'));
(handlerUrlPart, index) =>
handlerUrlPart === urlParts[index] ||
handlerUrlPart.startsWith(':')
);
// If there is a match... // If there is a match...
if (matches) { if ( matches )
{
// Assign the matching handler // Assign the matching handler
matchingHandler.handler = handler; matchingHandler.handler = handler;
// Extract and assign the parameters // Extract and assign the parameters
matchingHandler.urlParams = fromPairs( matchingHandler.urlParams = fromPairs(compact(handlerUrlParts.map((handlerUrlPart, index) =>
compact( handlerUrlPart.startsWith(':') ? [handlerUrlPart.substring(1), urlParts[index]] : undefined,
handlerUrlParts.map((handlerUrlPart, index) => )));
handlerUrlPart.startsWith(':')
? [handlerUrlPart.substring(1), urlParts[index]]
: undefined
)
)
);
} }
}); });
@ -98,7 +92,8 @@ export class FuseMockApiService {
* @param url - URL address of the mocked API endpoint * @param url - URL address of the mocked API endpoint
* @param delay - Delay of the response in milliseconds * @param delay - Delay of the response in milliseconds
*/ */
onGet(url: string, delay?: number): FuseMockApiHandler { onGet(url: string, delay?: number): FuseMockApiHandler
{
return this._registerHandler('get', url, delay); return this._registerHandler('get', url, delay);
} }
@ -108,7 +103,8 @@ export class FuseMockApiService {
* @param url - URL address of the mocked API endpoint * @param url - URL address of the mocked API endpoint
* @param delay - Delay of the response in milliseconds * @param delay - Delay of the response in milliseconds
*/ */
onPost(url: string, delay?: number): FuseMockApiHandler { onPost(url: string, delay?: number): FuseMockApiHandler
{
return this._registerHandler('post', url, delay); return this._registerHandler('post', url, delay);
} }
@ -118,7 +114,8 @@ export class FuseMockApiService {
* @param url - URL address of the mocked API endpoint * @param url - URL address of the mocked API endpoint
* @param delay - Delay of the response in milliseconds * @param delay - Delay of the response in milliseconds
*/ */
onPatch(url: string, delay?: number): FuseMockApiHandler { onPatch(url: string, delay?: number): FuseMockApiHandler
{
return this._registerHandler('patch', url, delay); return this._registerHandler('patch', url, delay);
} }
@ -128,7 +125,8 @@ export class FuseMockApiService {
* @param url - URL address of the mocked API endpoint * @param url - URL address of the mocked API endpoint
* @param delay - Delay of the response in milliseconds * @param delay - Delay of the response in milliseconds
*/ */
onDelete(url: string, delay?: number): FuseMockApiHandler { onDelete(url: string, delay?: number): FuseMockApiHandler
{
return this._registerHandler('delete', url, delay); return this._registerHandler('delete', url, delay);
} }
@ -138,7 +136,8 @@ export class FuseMockApiService {
* @param url - URL address of the mocked API endpoint * @param url - URL address of the mocked API endpoint
* @param delay - Delay of the response in milliseconds * @param delay - Delay of the response in milliseconds
*/ */
onPut(url: string, delay?: number): FuseMockApiHandler { onPut(url: string, delay?: number): FuseMockApiHandler
{
return this._registerHandler('put', url, delay); return this._registerHandler('put', url, delay);
} }
@ -148,7 +147,8 @@ export class FuseMockApiService {
* @param url - URL address of the mocked API endpoint * @param url - URL address of the mocked API endpoint
* @param delay - Delay of the response in milliseconds * @param delay - Delay of the response in milliseconds
*/ */
onHead(url: string, delay?: number): FuseMockApiHandler { onHead(url: string, delay?: number): FuseMockApiHandler
{
return this._registerHandler('head', url, delay); return this._registerHandler('head', url, delay);
} }
@ -158,7 +158,8 @@ export class FuseMockApiService {
* @param url - URL address of the mocked API endpoint * @param url - URL address of the mocked API endpoint
* @param delay - Delay of the response in milliseconds * @param delay - Delay of the response in milliseconds
*/ */
onJsonp(url: string, delay?: number): FuseMockApiHandler { onJsonp(url: string, delay?: number): FuseMockApiHandler
{
return this._registerHandler('jsonp', url, delay); return this._registerHandler('jsonp', url, delay);
} }
@ -168,7 +169,8 @@ export class FuseMockApiService {
* @param url - URL address of the mocked API endpoint * @param url - URL address of the mocked API endpoint
* @param delay - Delay of the response in milliseconds * @param delay - Delay of the response in milliseconds
*/ */
onOptions(url: string, delay?: number): FuseMockApiHandler { onOptions(url: string, delay?: number): FuseMockApiHandler
{
return this._registerHandler('options', url, delay); return this._registerHandler('options', url, delay);
} }
@ -184,11 +186,8 @@ export class FuseMockApiService {
* @param delay * @param delay
* @private * @private
*/ */
private _registerHandler( private _registerHandler(method: FuseMockApiMethods, url: string, delay?: number): FuseMockApiHandler
method: FuseMockApiMethods, {
url: string,
delay?: number
): FuseMockApiHandler {
// Create a new instance of FuseMockApiRequestHandler // Create a new instance of FuseMockApiRequestHandler
const fuseMockHttp = new FuseMockApiHandler(url, delay); const fuseMockHttp = new FuseMockApiHandler(url, delay);

View File

@ -2,10 +2,7 @@ import { HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
export type FuseMockApiReplyCallback = export type FuseMockApiReplyCallback =
| ((data: { | ((data: { request: HttpRequest<any>; urlParams: { [key: string]: string } }) => ([number, string | any]) | Observable<any>)
request: HttpRequest<any>;
urlParams: { [key: string]: string };
}) => [number, string | any] | Observable<any>)
| undefined; | undefined;
export type FuseMockApiMethods = export type FuseMockApiMethods =

View File

@ -1,4 +1,12 @@
export class FuseMockApiUtils { export class FuseMockApiUtils
{
/**
* Constructor
*/
constructor()
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Public methods // @ Public methods
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -6,23 +14,23 @@ export class FuseMockApiUtils {
/** /**
* Generate a globally unique id * Generate a globally unique id
*/ */
static guid(): string { static guid(): string
{
/* eslint-disable */ /* eslint-disable */
let d = new Date().getTime(); let d = new Date().getTime();
// Use high-precision timer if available // Use high-precision timer if available
if ( if ( typeof performance !== 'undefined' && typeof performance.now === 'function' )
typeof performance !== 'undefined' && {
typeof performance.now === 'function'
) {
d += performance.now(); d += performance.now();
} }
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) =>
{
const r = (d + Math.random() * 16) % 16 | 0; const r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16); d = Math.floor(d / 16);
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16); return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
}); });
/* eslint-enable */ /* eslint-enable */

View File

@ -4,11 +4,19 @@ import { Pipe, PipeTransform } from '@angular/core';
* Finds an object from given source using the given key - value pairs * Finds an object from given source using the given key - value pairs
*/ */
@Pipe({ @Pipe({
name: 'fuseFindByKey', name : 'fuseFindByKey',
pure: false, pure : false,
standalone: true, standalone: true,
}) })
export class FuseFindByKeyPipe implements PipeTransform { export class FuseFindByKeyPipe implements PipeTransform
{
/**
* Constructor
*/
constructor()
{
}
/** /**
* Transform * Transform
* *
@ -16,15 +24,15 @@ export class FuseFindByKeyPipe implements PipeTransform {
* @param key Key of the object property to look for * @param key Key of the object property to look for
* @param source Array of objects to find from * @param source Array of objects to find from
*/ */
transform(value: string | string[], key: string, source: any[]): any { transform(value: string | string[], key: string, source: any[]): any
{
// If the given value is an array of strings... // If the given value is an array of strings...
if (Array.isArray(value)) { if ( Array.isArray(value) )
return value.map((item) => {
source.find((sourceItem) => sourceItem[key] === item) return value.map(item => source.find(sourceItem => sourceItem[key] === item));
);
} }
// If the value is a string... // If the value is a string...
return source.find((sourceItem) => sourceItem[key] === value); return source.find(sourceItem => sourceItem[key] === value);
} }
} }

View File

@ -1,2 +1,2 @@
export * from '@fuse/pipes/find-by-key/find-by-key.module';
export * from '@fuse/pipes/find-by-key/find-by-key.pipe'; export * from '@fuse/pipes/find-by-key/find-by-key.pipe';
export * from '@fuse/pipes/find-by-key/find-by-key.module';

View File

@ -1,11 +1,21 @@
import { inject, Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import { FUSE_CONFIG } from '@fuse/services/config/config.constants'; import { FUSE_CONFIG } from '@fuse/services/config/config.constants';
import { merge } from 'lodash-es'; import { merge } from 'lodash-es';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
@Injectable({ providedIn: 'root' }) @Injectable({providedIn: 'root'})
export class FuseConfigService { export class FuseConfigService
private _config = new BehaviorSubject(inject(FUSE_CONFIG)); {
private _config: BehaviorSubject<any>;
/**
* Constructor
*/
constructor(@Inject(FUSE_CONFIG) config: any)
{
// Private
this._config = new BehaviorSubject(config);
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Accessors // @ Accessors
@ -14,7 +24,8 @@ export class FuseConfigService {
/** /**
* Setter & getter for config * Setter & getter for config
*/ */
set config(value: any) { set config(value: any)
{
// Merge the new config over to the current config // Merge the new config over to the current config
const config = merge({}, this._config.getValue(), value); const config = merge({}, this._config.getValue(), value);
@ -23,7 +34,8 @@ export class FuseConfigService {
} }
// eslint-disable-next-line @typescript-eslint/member-ordering // eslint-disable-next-line @typescript-eslint/member-ordering
get config$(): Observable<any> { get config$(): Observable<any>
{
return this._config.asObservable(); return this._config.asObservable();
} }
@ -34,7 +46,8 @@ export class FuseConfigService {
/** /**
* Resets the config to the default * Resets the config to the default
*/ */
reset(): void { reset(): void
{
// Set the config // Set the config
this._config.next(this.config); this._config.next(this.config);
} }

View File

@ -8,7 +8,8 @@ export type Themes = { id: string; name: string }[];
* AppConfig interface. Update this interface to strictly type your config * AppConfig interface. Update this interface to strictly type your config
* object. * object.
*/ */
export interface FuseConfig { export interface FuseConfig
{
layout: string; layout: string;
scheme: Scheme; scheme: Scheme;
screens: Screens; screens: Screens;

View File

@ -4,47 +4,54 @@ import { FuseConfirmationConfig } from '@fuse/services/confirmation/confirmation
import { FuseConfirmationDialogComponent } from '@fuse/services/confirmation/dialog/dialog.component'; import { FuseConfirmationDialogComponent } from '@fuse/services/confirmation/dialog/dialog.component';
import { merge } from 'lodash-es'; import { merge } from 'lodash-es';
@Injectable({ providedIn: 'root' }) @Injectable({providedIn: 'root'})
export class FuseConfirmationService { export class FuseConfirmationService
{
private _matDialog: MatDialog = inject(MatDialog); private _matDialog: MatDialog = inject(MatDialog);
private _defaultConfig: FuseConfirmationConfig = { private _defaultConfig: FuseConfirmationConfig = {
title: 'Confirm action', title : 'Confirm action',
message: 'Are you sure you want to confirm this action?', message : 'Are you sure you want to confirm this action?',
icon: { icon : {
show: true, show : true,
name: 'heroicons_outline:exclamation-triangle', name : 'heroicons_outline:exclamation-triangle',
color: 'warn', color: 'warn',
}, },
actions: { actions : {
confirm: { confirm: {
show: true, show : true,
label: 'Confirm', label: 'Confirm',
color: 'warn', color: 'warn',
}, },
cancel: { cancel : {
show: true, show : true,
label: 'Cancel', label: 'Cancel',
}, },
}, },
dismissible: false, dismissible: false,
}; };
/**
* Constructor
*/
constructor()
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Public methods // @ Public methods
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
open( open(config: FuseConfirmationConfig = {}): MatDialogRef<FuseConfirmationDialogComponent>
config: FuseConfirmationConfig = {} {
): MatDialogRef<FuseConfirmationDialogComponent> {
// Merge the user config with the default config // Merge the user config with the default config
const userConfig = merge({}, this._defaultConfig, config); const userConfig = merge({}, this._defaultConfig, config);
// Open the dialog // Open the dialog
return this._matDialog.open(FuseConfirmationDialogComponent, { return this._matDialog.open(FuseConfirmationDialogComponent, {
autoFocus: false, autoFocus : false,
disableClose: !userConfig.dismissible, disableClose: !userConfig.dismissible,
data: userConfig, data : userConfig,
panelClass: 'fuse-confirmation-dialog-panel', panelClass : 'fuse-confirmation-dialog-panel',
}); });
} }
} }

View File

@ -1,18 +1,11 @@
export interface FuseConfirmationConfig { export interface FuseConfirmationConfig
{
title?: string; title?: string;
message?: string; message?: string;
icon?: { icon?: {
show?: boolean; show?: boolean;
name?: string; name?: string;
color?: color?: 'primary' | 'accent' | 'warn' | 'basic' | 'info' | 'success' | 'warning' | 'error';
| 'primary'
| 'accent'
| 'warn'
| 'basic'
| 'info'
| 'success'
| 'warning'
| 'error';
}; };
actions?: { actions?: {
confirm?: { confirm?: {

View File

@ -1,95 +1,85 @@
<div class="relative flex h-full w-full flex-col"> <div class="relative flex flex-col w-full h-full">
<!-- Dismiss button --> <!-- Dismiss button -->
@if (data.dismissible) { <ng-container *ngIf="data.dismissible">
<div class="absolute right-0 top-0 pr-4 pt-4"> <div class="absolute top-0 right-0 pt-4 pr-4">
<button mat-icon-button [matDialogClose]="undefined"> <button
mat-icon-button
[matDialogClose]="undefined">
<mat-icon <mat-icon
class="text-secondary" class="text-secondary"
[svgIcon]="'heroicons_outline:x-mark'" [svgIcon]="'heroicons_outline:x-mark'"></mat-icon>
></mat-icon>
</button> </button>
</div> </div>
} </ng-container>
<!-- Content --> <!-- Content -->
<div <div class="flex flex-col sm:flex-row flex-auto items-center sm:items-start p-8 pb-6 sm:pb-8">
class="flex flex-auto flex-col items-center p-8 pb-6 sm:flex-row sm:items-start sm:pb-8"
>
<!-- Icon --> <!-- Icon -->
@if (data.icon.show) { <ng-container *ngIf="data.icon.show">
<div <div
class="flex h-10 w-10 flex-0 items-center justify-center rounded-full sm:mr-4" class="flex flex-0 items-center justify-center w-10 h-10 sm:mr-4 rounded-full"
[ngClass]="{ [ngClass]="{'text-primary-600 bg-primary-100 dark:text-primary-50 dark:bg-primary-600': data.icon.color === 'primary',
'bg-primary-100 text-primary-600 dark:bg-primary-600 dark:text-primary-50': 'text-accent-600 bg-accent-100 dark:text-accent-50 dark:bg-accent-600': data.icon.color === 'accent',
data.icon.color === 'primary', 'text-warn-600 bg-warn-100 dark:text-warn-50 dark:bg-warn-600': data.icon.color === 'warn',
'bg-accent-100 text-accent-600 dark:bg-accent-600 dark:text-accent-50': 'text-gray-600 bg-gray-100 dark:text-gray-50 dark:bg-gray-600': data.icon.color === 'basic',
data.icon.color === 'accent', 'text-blue-600 bg-blue-100 dark:text-blue-50 dark:bg-blue-600': data.icon.color === 'info',
'bg-warn-100 text-warn-600 dark:bg-warn-600 dark:text-warn-50': 'text-green-500 bg-green-100 dark:text-green-50 dark:bg-green-500': data.icon.color === 'success',
data.icon.color === 'warn', 'text-amber-500 bg-amber-100 dark:text-amber-50 dark:bg-amber-500': data.icon.color === 'warning',
'bg-gray-100 text-gray-600 dark:bg-gray-600 dark:text-gray-50': 'text-red-600 bg-red-100 dark:text-red-50 dark:bg-red-600': data.icon.color === 'error'
data.icon.color === 'basic', }">
'bg-blue-100 text-blue-600 dark:bg-blue-600 dark:text-blue-50':
data.icon.color === 'info',
'bg-green-100 text-green-500 dark:bg-green-500 dark:text-green-50':
data.icon.color === 'success',
'bg-amber-100 text-amber-500 dark:bg-amber-500 dark:text-amber-50':
data.icon.color === 'warning',
'bg-red-100 text-red-600 dark:bg-red-600 dark:text-red-50':
data.icon.color === 'error',
}"
>
<mat-icon <mat-icon
class="text-current" class="text-current"
[svgIcon]="data.icon.name" [svgIcon]="data.icon.name"></mat-icon>
></mat-icon>
</div> </div>
} </ng-container>
<ng-container *ngIf="data.title || data.message">
<div class="flex flex-col items-center sm:items-start mt-4 sm:mt-0 sm:pr-8 space-y-1 text-center sm:text-left">
@if (data.title || data.message) {
<div
class="mt-4 flex flex-col items-center space-y-1 text-center sm:mt-0 sm:items-start sm:pr-8 sm:text-left"
>
<!-- Title --> <!-- Title -->
@if (data.title) { <ng-container *ngIf="data.title">
<div <div
class="text-xl font-medium leading-6" class="text-xl leading-6 font-medium"
[innerHTML]="data.title" [innerHTML]="data.title"></div>
></div> </ng-container>
}
<!-- Message --> <!-- Message -->
@if (data.message) { <ng-container *ngIf="data.message">
<div <div
class="text-secondary" class="text-secondary"
[innerHTML]="data.message" [innerHTML]="data.message"></div>
></div> </ng-container>
}
</div> </div>
} </ng-container>
</div> </div>
<!-- Actions --> <!-- Actions -->
@if (data.actions.confirm.show || data.actions.cancel.show) { <ng-container *ngIf="data.actions.confirm.show || data.actions.cancel.show">
<div <div class="flex items-center justify-center sm:justify-end px-6 py-4 space-x-3 bg-gray-50 dark:bg-black dark:bg-opacity-10">
class="flex items-center justify-center space-x-3 bg-gray-50 px-6 py-4 dark:bg-black dark:bg-opacity-10 sm:justify-end"
>
<!-- Cancel --> <!-- Cancel -->
@if (data.actions.cancel.show) { <ng-container *ngIf="data.actions.cancel.show">
<button mat-stroked-button [matDialogClose]="'cancelled'"> <button
{{ data.actions.cancel.label }} mat-stroked-button
[matDialogClose]="'cancelled'">
{{data.actions.cancel.label}}
</button> </button>
} </ng-container>
<!-- Confirm --> <!-- Confirm -->
@if (data.actions.confirm.show) { <ng-container *ngIf="data.actions.confirm.show">
<button <button
mat-flat-button mat-flat-button
[color]="data.actions.confirm.color" [color]="data.actions.confirm.color"
[matDialogClose]="'confirmed'" [matDialogClose]="'confirmed'">
> {{data.actions.confirm.label}}
{{ data.actions.confirm.label }}
</button> </button>
} </ng-container>
</div> </div>
} </ng-container>
</div> </div>

View File

@ -1,21 +1,23 @@
import { NgClass } from '@angular/common'; import { NgClass, NgIf } from '@angular/common';
import { Component, ViewEncapsulation, inject } from '@angular/core'; import { Component, Inject, ViewEncapsulation } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { FuseConfirmationConfig } from '@fuse/services/confirmation/confirmation.types'; import { FuseConfirmationConfig } from '@fuse/services/confirmation/confirmation.types';
@Component({ @Component({
selector: 'fuse-confirmation-dialog', selector : 'fuse-confirmation-dialog',
templateUrl: './dialog.component.html', templateUrl : './dialog.component.html',
styles: [ styles : [
` `
.fuse-confirmation-dialog-panel { .fuse-confirmation-dialog-panel {
@screen md { @screen md {
@apply w-128; @apply w-128;
} }
.mat-mdc-dialog-container { .mat-mdc-dialog-container {
.mat-mdc-dialog-surface { .mat-mdc-dialog-surface {
padding: 0 !important; padding: 0 !important;
} }
@ -24,8 +26,16 @@ import { FuseConfirmationConfig } from '@fuse/services/confirmation/confirmation
`, `,
], ],
encapsulation: ViewEncapsulation.None, encapsulation: ViewEncapsulation.None,
imports: [MatButtonModule, MatDialogModule, MatIconModule, NgClass], standalone : true,
imports : [NgIf, MatButtonModule, MatDialogModule, MatIconModule, NgClass],
}) })
export class FuseConfirmationDialogComponent { export class FuseConfirmationDialogComponent
data: FuseConfirmationConfig = inject(MAT_DIALOG_DATA); {
/**
* Constructor
*/
constructor(@Inject(MAT_DIALOG_DATA) public data: FuseConfirmationConfig)
{
}
} }

View File

@ -1,21 +1,23 @@
import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http'; import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http';
import { inject } from '@angular/core'; import { inject } from '@angular/core';
import { FuseLoadingService } from '@fuse/services/loading/loading.service'; import { FuseLoadingService } from '@fuse/services/loading/loading.service';
import { Observable, finalize, take } from 'rxjs'; import { finalize, Observable, take } from 'rxjs';
export const fuseLoadingInterceptor = ( export const fuseLoadingInterceptor = (req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> =>
req: HttpRequest<unknown>, {
next: HttpHandlerFn
): Observable<HttpEvent<unknown>> => {
const fuseLoadingService = inject(FuseLoadingService); const fuseLoadingService = inject(FuseLoadingService);
let handleRequestsAutomatically = false; let handleRequestsAutomatically = false;
fuseLoadingService.auto$.pipe(take(1)).subscribe((value) => { fuseLoadingService.auto$
handleRequestsAutomatically = value; .pipe(take(1))
}); .subscribe((value) =>
{
handleRequestsAutomatically = value;
});
// If the Auto mode is turned off, do nothing // If the Auto mode is turned off, do nothing
if (!handleRequestsAutomatically) { if ( !handleRequestsAutomatically )
{
return next(req); return next(req);
} }
@ -23,9 +25,9 @@ export const fuseLoadingInterceptor = (
fuseLoadingService._setLoadingStatus(true, req.url); fuseLoadingService._setLoadingStatus(true, req.url);
return next(req).pipe( return next(req).pipe(
finalize(() => { finalize(() =>
{
// Set the status to false if there are any errors or the request is completed // Set the status to false if there are any errors or the request is completed
fuseLoadingService._setLoadingStatus(false, req.url); fuseLoadingService._setLoadingStatus(false, req.url);
}) }));
);
}; };

View File

@ -1,21 +1,23 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
@Injectable({ providedIn: 'root' }) @Injectable({providedIn: 'root'})
export class FuseLoadingService { export class FuseLoadingService
private _auto$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>( {
true private _auto$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
); private _mode$: BehaviorSubject<'determinate' | 'indeterminate'> = new BehaviorSubject<'determinate' | 'indeterminate'>('indeterminate');
private _mode$: BehaviorSubject<'determinate' | 'indeterminate'> = private _progress$: BehaviorSubject<number | null> = new BehaviorSubject<number | null>(0);
new BehaviorSubject<'determinate' | 'indeterminate'>('indeterminate'); private _show$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
private _progress$: BehaviorSubject<number | null> = new BehaviorSubject<
number | null
>(0);
private _show$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(
false
);
private _urlMap: Map<string, boolean> = new Map<string, boolean>(); private _urlMap: Map<string, boolean> = new Map<string, boolean>();
/**
* Constructor
*/
constructor(private _httpClient: HttpClient)
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Accessors // @ Accessors
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -23,28 +25,32 @@ export class FuseLoadingService {
/** /**
* Getter for auto mode * Getter for auto mode
*/ */
get auto$(): Observable<boolean> { get auto$(): Observable<boolean>
{
return this._auto$.asObservable(); return this._auto$.asObservable();
} }
/** /**
* Getter for mode * Getter for mode
*/ */
get mode$(): Observable<'determinate' | 'indeterminate'> { get mode$(): Observable<'determinate' | 'indeterminate'>
{
return this._mode$.asObservable(); return this._mode$.asObservable();
} }
/** /**
* Getter for progress * Getter for progress
*/ */
get progress$(): Observable<number> { get progress$(): Observable<number>
{
return this._progress$.asObservable(); return this._progress$.asObservable();
} }
/** /**
* Getter for show * Getter for show
*/ */
get show$(): Observable<boolean> { get show$(): Observable<boolean>
{
return this._show$.asObservable(); return this._show$.asObservable();
} }
@ -55,14 +61,16 @@ export class FuseLoadingService {
/** /**
* Show the loading bar * Show the loading bar
*/ */
show(): void { show(): void
{
this._show$.next(true); this._show$.next(true);
} }
/** /**
* Hide the loading bar * Hide the loading bar
*/ */
hide(): void { hide(): void
{
this._show$.next(false); this._show$.next(false);
} }
@ -71,7 +79,8 @@ export class FuseLoadingService {
* *
* @param value * @param value
*/ */
setAutoMode(value: boolean): void { setAutoMode(value: boolean): void
{
this._auto$.next(value); this._auto$.next(value);
} }
@ -80,7 +89,8 @@ export class FuseLoadingService {
* *
* @param value * @param value
*/ */
setMode(value: 'determinate' | 'indeterminate'): void { setMode(value: 'determinate' | 'indeterminate'): void
{
this._mode$.next(value); this._mode$.next(value);
} }
@ -89,8 +99,10 @@ export class FuseLoadingService {
* *
* @param value * @param value
*/ */
setProgress(value: number): void { setProgress(value: number): void
if (value < 0 || value > 100) { {
if ( value < 0 || value > 100 )
{
console.error('Progress value must be between 0 and 100!'); console.error('Progress value must be between 0 and 100!');
return; return;
} }
@ -104,22 +116,28 @@ export class FuseLoadingService {
* @param status * @param status
* @param url * @param url
*/ */
_setLoadingStatus(status: boolean, url: string): void { _setLoadingStatus(status: boolean, url: string): void
{
// Return if the url was not provided // Return if the url was not provided
if (!url) { if ( !url )
{
console.error('The request URL must be provided!'); console.error('The request URL must be provided!');
return; return;
} }
if (status === true) { if ( status === true )
{
this._urlMap.set(url, status); this._urlMap.set(url, status);
this._show$.next(true); this._show$.next(true);
} else if (status === false && this._urlMap.has(url)) { }
else if ( status === false && this._urlMap.has(url) )
{
this._urlMap.delete(url); this._urlMap.delete(url);
} }
// Only set the status to 'false' if all outgoing requests are completed // Only set the status to 'false' if all outgoing requests are completed
if (this._urlMap.size === 0) { if ( this._urlMap.size === 0 )
{
this._show$.next(false); this._show$.next(false);
} }
} }

View File

@ -1,2 +1,2 @@
export * from '@fuse/services/loading/loading.interceptor';
export * from '@fuse/services/loading/loading.service'; export * from '@fuse/services/loading/loading.service';
export * from '@fuse/services/loading/loading.interceptor';

View File

@ -1,74 +1,54 @@
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
import { Injectable, inject } from '@angular/core'; import { Injectable } from '@angular/core';
import { FuseConfigService } from '@fuse/services/config'; import { FuseConfigService } from '@fuse/services/config';
import { fromPairs } from 'lodash-es'; import { fromPairs } from 'lodash-es';
import { Observable, ReplaySubject, map, switchMap } from 'rxjs'; import { map, Observable, ReplaySubject, switchMap } from 'rxjs';
@Injectable({ providedIn: 'root' }) @Injectable({providedIn: 'root'})
export class FuseMediaWatcherService { export class FuseMediaWatcherService
private _breakpointObserver = inject(BreakpointObserver); {
private _fuseConfigService = inject(FuseConfigService); private _onMediaChange: ReplaySubject<{ matchingAliases: string[]; matchingQueries: any }> = new ReplaySubject<{ matchingAliases: string[]; matchingQueries: any }>(1);
private _onMediaChange: ReplaySubject<{
matchingAliases: string[];
matchingQueries: any;
}> = new ReplaySubject<{ matchingAliases: string[]; matchingQueries: any }>(
1
);
/** /**
* Constructor * Constructor
*/ */
constructor() { constructor(
this._fuseConfigService.config$ private _breakpointObserver: BreakpointObserver,
.pipe( private _fuseConfigService: FuseConfigService,
map((config) => )
fromPairs( {
Object.entries(config.screens).map( this._fuseConfigService.config$.pipe(
([alias, screen]) => [ map(config => fromPairs(Object.entries(config.screens).map(([alias, screen]) => ([alias, `(min-width: ${screen})`])))),
alias, switchMap(screens => this._breakpointObserver.observe(Object.values(screens)).pipe(
`(min-width: ${screen})`, map((state) =>
] {
) // Prepare the observable values and set their defaults
) const matchingAliases: string[] = [];
), const matchingQueries: any = {};
switchMap((screens) =>
this._breakpointObserver
.observe(Object.values(screens))
.pipe(
map((state) => {
// Prepare the observable values and set their defaults
const matchingAliases: string[] = [];
const matchingQueries: any = {};
// Get the matching breakpoints and use them to fill the subject // Get the matching breakpoints and use them to fill the subject
const matchingBreakpoints = const matchingBreakpoints = Object.entries(state.breakpoints).filter(([query, matches]) => matches) ?? [];
Object.entries(state.breakpoints).filter( for ( const [query] of matchingBreakpoints )
([query, matches]) => matches {
) ?? []; // Find the alias of the matching query
for (const [query] of matchingBreakpoints) { const matchingAlias = Object.entries(screens).find(([alias, q]) => q === query)[0];
// Find the alias of the matching query
const matchingAlias = Object.entries(
screens
).find(([alias, q]) => q === query)[0];
// Add the matching query to the observable values // Add the matching query to the observable values
if (matchingAlias) { if ( matchingAlias )
matchingAliases.push(matchingAlias); {
matchingQueries[matchingAlias] = query; matchingAliases.push(matchingAlias);
} matchingQueries[matchingAlias] = query;
} }
}
// Execute the observable // Execute the observable
this._onMediaChange.next({ this._onMediaChange.next({
matchingAliases, matchingAliases,
matchingQueries, matchingQueries,
}); });
}) }),
) )),
) ).subscribe();
)
.subscribe();
} }
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -78,10 +58,8 @@ export class FuseMediaWatcherService {
/** /**
* Getter for _onMediaChange * Getter for _onMediaChange
*/ */
get onMediaChange$(): Observable<{ get onMediaChange$(): Observable<{ matchingAliases: string[]; matchingQueries: any }>
matchingAliases: string[]; {
matchingQueries: any;
}> {
return this._onMediaChange.asObservable(); return this._onMediaChange.asObservable();
} }
@ -94,7 +72,8 @@ export class FuseMediaWatcherService {
* *
* @param query * @param query
*/ */
onMediaQueryChange$(query: string | string[]): Observable<BreakpointState> { onMediaQueryChange$(query: string | string[]): Observable<BreakpointState>
{
return this._breakpointObserver.observe(query); return this._breakpointObserver.observe(query);
} }
} }

View File

@ -1,49 +1,57 @@
import { Platform } from '@angular/cdk/platform'; import { Platform } from '@angular/cdk/platform';
import { inject, Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class FusePlatformService {
private _platform = inject(Platform);
@Injectable({providedIn: 'root'})
export class FusePlatformService
{
osName = 'os-unknown'; osName = 'os-unknown';
/** /**
* Constructor * Constructor
*/ */
constructor() { constructor(private _platform: Platform)
{
// If the platform is not a browser, return immediately // If the platform is not a browser, return immediately
if (!this._platform.isBrowser) { if ( !this._platform.isBrowser )
{
return; return;
} }
// Windows // Windows
if (navigator.userAgent.includes('Win')) { if ( navigator.userAgent.includes('Win') )
{
this.osName = 'os-windows'; this.osName = 'os-windows';
} }
// Mac OS // Mac OS
if (navigator.userAgent.includes('Mac')) { if ( navigator.userAgent.includes('Mac') )
{
this.osName = 'os-mac'; this.osName = 'os-mac';
} }
// Unix // Unix
if (navigator.userAgent.includes('X11')) { if ( navigator.userAgent.includes('X11') )
{
this.osName = 'os-unix'; this.osName = 'os-unix';
} }
// Linux // Linux
if (navigator.userAgent.includes('Linux')) { if ( navigator.userAgent.includes('Linux') )
{
this.osName = 'os-linux'; this.osName = 'os-linux';
} }
// iOS // iOS
if (this._platform.IOS) { if ( this._platform.IOS )
{
this.osName = 'os-ios'; this.osName = 'os-ios';
} }
// Android // Android
if (this._platform.ANDROID) { if ( this._platform.ANDROID )
{
this.osName = 'os-android'; this.osName = 'os-android';
} }
} }
} }

View File

@ -1,24 +1,27 @@
import { DOCUMENT } from '@angular/common'; import { DOCUMENT } from '@angular/common';
import { inject, Injectable } from '@angular/core'; import { Inject, Injectable } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router'; import { NavigationEnd, Router } from '@angular/router';
import { filter, take } from 'rxjs'; import { filter, take } from 'rxjs';
@Injectable({ providedIn: 'root' }) @Injectable({providedIn: 'root'})
export class FuseSplashScreenService { export class FuseSplashScreenService
private _document = inject(DOCUMENT); {
private _router = inject(Router);
/** /**
* Constructor * Constructor
*/ */
constructor() { constructor(
@Inject(DOCUMENT) private _document: any,
private _router: Router,
)
{
// Hide it on the first NavigationEnd event // Hide it on the first NavigationEnd event
this._router.events this._router.events
.pipe( .pipe(
filter((event) => event instanceof NavigationEnd), filter(event => event instanceof NavigationEnd),
take(1) take(1),
) )
.subscribe(() => { .subscribe(() =>
{
this.hide(); this.hide();
}); });
} }
@ -30,14 +33,16 @@ export class FuseSplashScreenService {
/** /**
* Show the splash screen * Show the splash screen
*/ */
show(): void { show(): void
{
this._document.body.classList.remove('fuse-splash-screen-hidden'); this._document.body.classList.remove('fuse-splash-screen-hidden');
} }
/** /**
* Hide the splash screen * Hide the splash screen
*/ */
hide(): void { hide(): void
{
this._document.body.classList.add('fuse-splash-screen-hidden'); this._document.body.classList.add('fuse-splash-screen-hidden');
} }
} }

View File

@ -1,8 +1,16 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { IsActiveMatchOptions } from '@angular/router'; import { IsActiveMatchOptions } from '@angular/router';
@Injectable({ providedIn: 'root' }) @Injectable({providedIn: 'root'})
export class FuseUtilsService { export class FuseUtilsService
{
/**
* Constructor
*/
constructor()
{
}
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
// @ Accessors // @ Accessors
// ----------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------
@ -10,24 +18,26 @@ export class FuseUtilsService {
/** /**
* Get the equivalent "IsActiveMatchOptions" options for "exact = true". * Get the equivalent "IsActiveMatchOptions" options for "exact = true".
*/ */
get exactMatchOptions(): IsActiveMatchOptions { get exactMatchOptions(): IsActiveMatchOptions
{
return { return {
paths: 'exact', paths : 'exact',
fragment: 'ignored', fragment : 'ignored',
matrixParams: 'ignored', matrixParams: 'ignored',
queryParams: 'exact', queryParams : 'exact',
}; };
} }
/** /**
* Get the equivalent "IsActiveMatchOptions" options for "exact = false". * Get the equivalent "IsActiveMatchOptions" options for "exact = false".
*/ */
get subsetMatchOptions(): IsActiveMatchOptions { get subsetMatchOptions(): IsActiveMatchOptions
{
return { return {
paths: 'subset', paths : 'subset',
fragment: 'ignored', fragment : 'ignored',
matrixParams: 'ignored', matrixParams: 'ignored',
queryParams: 'subset', queryParams : 'subset',
}; };
} }
@ -40,12 +50,13 @@ export class FuseUtilsService {
* *
* @param length * @param length
*/ */
randomId(length: number = 10): string { randomId(length: number = 10): string
const chars = {
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let name = ''; let name = '';
for (let i = 0; i < 10; i++) { for ( let i = 0; i < 10; i++ )
{
name += chars.charAt(Math.floor(Math.random() * chars.length)); name += chars.charAt(Math.floor(Math.random() * chars.length));
} }

View File

@ -6,7 +6,7 @@
flex-direction: column; flex-direction: column;
margin: 32px 0; margin: 32px 0;
overflow: hidden; overflow: hidden;
@apply bg-card rounded-2xl shadow; @apply rounded-2xl shadow bg-card;
.title { .title {
display: flex; display: flex;
@ -32,8 +32,11 @@
} }
mat-tab-group { mat-tab-group {
.mat-tab-body-content { .mat-tab-body-content {
.fuse-highlight { .fuse-highlight {
pre { pre {
margin: 0; margin: 0;
border-radius: 0; border-radius: 0;

View File

@ -8,8 +8,8 @@
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* @ Font smoothing /* @ Font smoothing
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
*[class*='mat-'], *[class*=mat-],
*[class*='mat-mdc-'] { *[class*=mat-mdc-] {
-webkit-font-smoothing: auto !important; -webkit-font-smoothing: auto !important;
-moz-osx-font-smoothing: auto !important; -moz-osx-font-smoothing: auto !important;
@ -23,10 +23,11 @@
/* @ Accordion /* @ Accordion
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
.mat-accordion { .mat-accordion {
.mat-expansion-panel { .mat-expansion-panel {
margin-bottom: 24px; margin-bottom: 24px;
border-radius: 8px !important; border-radius: 8px !important;
transition: box-shadow 225ms cubic-bezier(0.4, 0, 0.2, 1); transition: box-shadow 225ms cubic-bezier(0.4, 0.0, 0.2, 1);
@apply shadow #{'!important'}; @apply shadow #{'!important'};
&:last-child { &:last-child {
@ -39,8 +40,11 @@
} }
&:not(.mat-expanded) { &:not(.mat-expanded) {
.mat-expansion-panel-header { .mat-expansion-panel-header {
&:not([aria-disabled='true']) {
&:not([aria-disabled=true]) {
&.cdk-keyboard-focused, &.cdk-keyboard-focused,
&.cdk-program-focused, &.cdk-program-focused,
&:hover { &:hover {
@ -53,7 +57,8 @@
.mat-expansion-panel-header { .mat-expansion-panel-header {
font-size: 14px; font-size: 14px;
&[aria-disabled='true'] { &[aria-disabled=true] {
.mat-expansion-panel-header-description { .mat-expansion-panel-header-description {
margin-right: 28px; margin-right: 28px;
} }
@ -102,7 +107,8 @@
} }
/* Lower the icon opacity on disabled buttons */ /* Lower the icon opacity on disabled buttons */
&[disabled='true'] { &[disabled=true] {
.mat-icon { .mat-icon {
opacity: 0.38 !important; opacity: 0.38 !important;
} }
@ -151,11 +157,13 @@
border-radius: 9999px !important; border-radius: 9999px !important;
} }
/* Fix the alignment of icons when used within buttons */ /* Fix the alignment of icons when used within buttons */
.mat-mdc-button, .mat-mdc-button,
.mat-mdc-raised-button, .mat-mdc-raised-button,
.mat-mdc-outlined-button, .mat-mdc-outlined-button,
.mat-mdc-unelevated-button { .mat-mdc-unelevated-button {
& > .mat-icon { & > .mat-icon {
margin-left: 0 !important; margin-left: 0 !important;
margin-right: 0 !important; margin-right: 0 !important;
@ -170,8 +178,11 @@
.mat-mdc-icon-button, .mat-mdc-icon-button,
.mat-mdc-fab, .mat-mdc-fab,
.mat-mdc-mini-fab { .mat-mdc-mini-fab {
.mat-mdc-progress-spinner { .mat-mdc-progress-spinner {
.mdc-circular-progress__indeterminate-container { .mdc-circular-progress__indeterminate-container {
circle { circle {
stroke: currentColor !important; stroke: currentColor !important;
animation-duration: 6000ms; animation-duration: 6000ms;
@ -185,15 +196,12 @@
.mat-mdc-unelevated-button, .mat-mdc-unelevated-button,
.mat-mdc-fab, .mat-mdc-fab,
.mat-mdc-mini-fab { .mat-mdc-mini-fab {
--mat-mdc-button-persistent-ripple-color: theme(
'colors.gray[400]' --mat-mdc-button-persistent-ripple-color: theme('colors.gray[400]') !important;
) !important;
--mat-mdc-button-ripple-color: rgba(0, 0, 0, 0.1) !important; --mat-mdc-button-ripple-color: rgba(0, 0, 0, 0.1) !important;
.dark & { .dark & {
--mat-mdc-button-persistent-ripple-color: theme( --mat-mdc-button-persistent-ripple-color: theme('colors.black') !important;
'colors.black'
) !important;
--mat-mdc-button-ripple-color: rgba(0, 0, 0, 0.1) !important; --mat-mdc-button-ripple-color: rgba(0, 0, 0, 0.1) !important;
} }
@ -210,9 +218,12 @@
.mat-mdc-button, .mat-mdc-button,
.mat-mdc-icon-button, .mat-mdc-icon-button,
.mat-mdc-outlined-button { .mat-mdc-outlined-button {
&:not([disabled='true']) {
&:not([disabled=true]) {
/* Apply primary color */ /* Apply primary color */
&.mat-primary { &.mat-primary {
.mat-icon { .mat-icon {
@apply text-primary #{'!important'}; @apply text-primary #{'!important'};
} }
@ -220,6 +231,7 @@
/* Apply accent color */ /* Apply accent color */
&.mat-accent { &.mat-accent {
.mat-icon { .mat-icon {
@apply text-accent #{'!important'}; @apply text-accent #{'!important'};
} }
@ -227,6 +239,7 @@
/* Apply warn color */ /* Apply warn color */
&.mat-warn { &.mat-warn {
.mat-icon { .mat-icon {
@apply text-warn #{'!important'}; @apply text-warn #{'!important'};
} }
@ -236,19 +249,21 @@
/* Adjust the border color of outlined buttons */ /* Adjust the border color of outlined buttons */
.mat-mdc-outlined-button { .mat-mdc-outlined-button {
/* Not disabled */ /* Not disabled */
&:not([disabled='true']) { &:not([disabled=true]) {
@apply border-gray-300 dark:border-gray-500 #{'!important'}; @apply border-gray-300 dark:border-gray-500 #{'!important'};
} }
/* Disabled */ /* Disabled */
&[disabled='true'] { &[disabled=true] {
@apply border-gray-300/70 dark:border-gray-600 #{'!important'}; @apply border-gray-300/70 dark:border-gray-600 #{'!important'};
} }
} }
/* Don't wrap the button label text */ /* Don't wrap the button label text */
.mdc-button { .mdc-button {
.mdc-button__label { .mdc-button__label {
white-space: nowrap; white-space: nowrap;
} }
@ -262,6 +277,7 @@
@apply space-x-1; @apply space-x-1;
&.mat-button-toggle-group-appearance-standard { &.mat-button-toggle-group-appearance-standard {
.mat-button-toggle + .mat-button-toggle { .mat-button-toggle + .mat-button-toggle {
background-clip: padding-box; background-clip: padding-box;
} }
@ -274,6 +290,7 @@
font-weight: 500; font-weight: 500;
&.mat-button-toggle-checked { &.mat-button-toggle-checked {
.mat-button-toggle-label-content { .mat-button-toggle-label-content {
@apply text-default #{'!important'}; @apply text-default #{'!important'};
} }
@ -313,6 +330,7 @@
/* @ Dialog /* @ Dialog
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
.mat-mdc-dialog-container { .mat-mdc-dialog-container {
.mdc-dialog__surface { .mdc-dialog__surface {
border-radius: 16px !important; border-radius: 16px !important;
padding: 24px; padding: 24px;
@ -332,6 +350,7 @@
/* "fill" appearance */ /* "fill" appearance */
.mat-mdc-form-field.mat-form-field-appearance-fill { .mat-mdc-form-field.mat-form-field-appearance-fill {
/* Disabled */ /* Disabled */
&.mat-form-field-disabled { &.mat-form-field-disabled {
opacity: 0.7 !important; opacity: 0.7 !important;
@ -339,6 +358,7 @@
/* Invalid */ /* Invalid */
&.mat-form-field-invalid { &.mat-form-field-invalid {
/* Border color */ /* Border color */
.mat-mdc-text-field-wrapper { .mat-mdc-text-field-wrapper {
@apply border-warn dark:border-warn #{'!important'}; @apply border-warn dark:border-warn #{'!important'};
@ -346,6 +366,7 @@
/* Select */ /* Select */
.mat-mdc-select { .mat-mdc-select {
/* Placeholder color */ /* Placeholder color */
.mat-mdc-select-placeholder { .mat-mdc-select-placeholder {
@apply text-warn #{'!important'}; @apply text-warn #{'!important'};
@ -355,6 +376,7 @@
/* Hover */ /* Hover */
&:hover { &:hover {
.mat-mdc-form-field-focus-overlay { .mat-mdc-form-field-focus-overlay {
opacity: 0 !important; opacity: 0 !important;
} }
@ -362,6 +384,7 @@
/* Focused */ /* Focused */
&.mat-focused { &.mat-focused {
.mat-mdc-form-field-focus-overlay { .mat-mdc-form-field-focus-overlay {
opacity: 0 !important; opacity: 0 !important;
} }
@ -369,6 +392,7 @@
/* Focused and valid fields */ /* Focused and valid fields */
&.mat-focused:not(.mat-form-field-invalid) { &.mat-focused:not(.mat-form-field-invalid) {
/* Border color */ /* Border color */
.mat-mdc-text-field-wrapper { .mat-mdc-text-field-wrapper {
@apply border-primary dark:border-primary #{'!important'}; @apply border-primary dark:border-primary #{'!important'};
@ -377,7 +401,9 @@
/* Remove the default arrow for native select */ /* Remove the default arrow for native select */
&.mat-mdc-form-field-type-mat-native-select { &.mat-mdc-form-field-type-mat-native-select {
.mat-mdc-form-field-infix { .mat-mdc-form-field-infix {
select { select {
top: auto; top: auto;
margin-top: 0; margin-top: 0;
@ -406,7 +432,7 @@
border-radius: 6px; border-radius: 6px;
border-width: 1px; border-width: 1px;
border-style: solid; border-style: solid;
@apply border-gray-300 bg-white shadow-sm dark:border-gray-500 dark:bg-black dark:bg-opacity-5 #{'!important'}; @apply shadow-sm bg-white border-gray-300 dark:bg-black dark:bg-opacity-5 dark:border-gray-500 #{'!important'};
/* Adjust the top spacing and overflow when mat-label present */ /* Adjust the top spacing and overflow when mat-label present */
&:not(.mdc-text-field--no-label) { &:not(.mdc-text-field--no-label) {
@ -503,16 +529,17 @@
align-items: center; align-items: center;
&:focus { &:focus {
.mat-mdc-select-trigger { .mat-mdc-select-trigger {
.mat-mdc-select-value { .mat-mdc-select-value {
@apply text-primary #{'!important'}; @apply text-primary #{'!important'};
} }
.mat-mdc-select-arrow-wrapper { .mat-mdc-select-arrow-wrapper {
.mat-mdc-select-arrow { .mat-mdc-select-arrow {
border-top-color: var( border-top-color: var(--fuse-primary) !important;
--fuse-primary
) !important;
} }
} }
} }
@ -527,6 +554,7 @@
max-width: none; max-width: none;
mat-mdc-select-trigger { mat-mdc-select-trigger {
.mat-icon { .mat-icon {
margin: 0 !important; margin: 0 !important;
} }
@ -606,8 +634,11 @@
/* Adds better alignment for textarea inputs */ /* Adds better alignment for textarea inputs */
&:has(textarea.mat-mdc-input-element) { &:has(textarea.mat-mdc-input-element) {
.mat-mdc-text-field-wrapper { .mat-mdc-text-field-wrapper {
.mat-mdc-form-field-flex { .mat-mdc-form-field-flex {
.mat-mdc-form-field-icon-prefix, .mat-mdc-form-field-icon-prefix,
.mat-mdc-form-field-text-prefix, .mat-mdc-form-field-text-prefix,
.mat-mdc-form-field-icon-suffix, .mat-mdc-form-field-icon-suffix,
@ -621,14 +652,18 @@
/* Rounded */ /* Rounded */
&.fuse-mat-rounded { &.fuse-mat-rounded {
.mat-mdc-text-field-wrapper { .mat-mdc-text-field-wrapper {
border-radius: 24px; border-radius: 24px;
} }
/* Emphasized affix */ /* Emphasized affix */
&.fuse-mat-emphasized-affix { &.fuse-mat-emphasized-affix {
.mat-mdc-text-field-wrapper { .mat-mdc-text-field-wrapper {
.mat-mdc-form-field-flex { .mat-mdc-form-field-flex {
.mat-mdc-form-field-icon-prefix, .mat-mdc-form-field-icon-prefix,
.mat-mdc-form-field-text-prefix { .mat-mdc-form-field-text-prefix {
border-radius: 24px 0 0 24px; border-radius: 24px 0 0 24px;
@ -649,9 +684,7 @@
margin-right: 4px; margin-right: 4px;
} }
> *:not(.mat-icon):not(.mat-mdc-icon-button):not( > *:not(.mat-icon):not(.mat-mdc-icon-button):not(.mat-mdc-select):not(.mat-datepicker-toggle) {
.mat-mdc-select
):not(.mat-datepicker-toggle) {
margin-right: 12px; margin-right: 12px;
} }
} }
@ -676,9 +709,7 @@
margin-left: 4px !important; margin-left: 4px !important;
} }
> *:not(.mat-icon):not(.mat-mdc-icon-button):not( > *:not(.mat-icon):not(.mat-mdc-icon-button):not(.mat-mdc-select):not(.mat-datepicker-toggle) {
.mat-mdc-select
):not(.mat-datepicker-toggle) {
margin-left: 12px !important; margin-left: 12px !important;
} }
} }
@ -689,12 +720,16 @@
/* Dense */ /* Dense */
&.fuse-mat-dense { &.fuse-mat-dense {
.mat-mdc-text-field-wrapper { .mat-mdc-text-field-wrapper {
.mat-mdc-form-field-flex { .mat-mdc-form-field-flex {
.mat-mdc-form-field-icon-prefix, .mat-mdc-form-field-icon-prefix,
.mat-mdc-form-field-text-prefix, .mat-mdc-form-field-text-prefix,
.mat-mdc-form-field-icon-suffix, .mat-mdc-form-field-icon-suffix,
.mat-mdc-form-field-text-suffix { .mat-mdc-form-field-text-suffix {
.mat-mdc-icon-button { .mat-mdc-icon-button {
width: 32px !important; width: 32px !important;
min-width: 32px; min-width: 32px;
@ -705,6 +740,7 @@
.mat-mdc-form-field-icon-prefix, .mat-mdc-form-field-icon-prefix,
.mat-mdc-form-field-text-prefix { .mat-mdc-form-field-text-prefix {
> .mat-mdc-icon-button { > .mat-mdc-icon-button {
margin-left: -6px; margin-left: -6px;
margin-right: 12px; margin-right: 12px;
@ -713,6 +749,7 @@
.mat-mdc-form-field-icon-suffix, .mat-mdc-form-field-icon-suffix,
.mat-mdc-form-field-text-suffix { .mat-mdc-form-field-text-suffix {
> .mat-mdc-icon-button { > .mat-mdc-icon-button {
margin-left: 12px; margin-left: 12px;
margin-right: -6px; margin-right: -6px;
@ -732,8 +769,11 @@
/* Adds better alignment for textarea inputs */ /* Adds better alignment for textarea inputs */
&:has(textarea.mat-mdc-input-element) { &:has(textarea.mat-mdc-input-element) {
.mat-mdc-text-field-wrapper { .mat-mdc-text-field-wrapper {
.mat-mdc-form-field-flex { .mat-mdc-form-field-flex {
.mat-mdc-form-field-icon-prefix, .mat-mdc-form-field-icon-prefix,
.mat-mdc-form-field-text-prefix, .mat-mdc-form-field-text-prefix,
.mat-mdc-form-field-icon-suffix, .mat-mdc-form-field-icon-suffix,
@ -746,14 +786,18 @@
/* Rounded */ /* Rounded */
&.fuse-mat-rounded { &.fuse-mat-rounded {
.mat-mdc-text-field-wrapper { .mat-mdc-text-field-wrapper {
border-radius: 20px; border-radius: 20px;
} }
/* Emphasized affix */ /* Emphasized affix */
&.fuse-mat-emphasized-affix { &.fuse-mat-emphasized-affix {
.mat-mdc-text-field-wrapper { .mat-mdc-text-field-wrapper {
.mat-mdc-form-field-flex { .mat-mdc-form-field-flex {
.mat-mdc-form-field-icon-prefix, .mat-mdc-form-field-icon-prefix,
.mat-mdc-form-field-text-prefix { .mat-mdc-form-field-text-prefix {
border-radius: 20px 0 0 20px !important; border-radius: 20px 0 0 20px !important;
@ -771,8 +815,11 @@
/* Emphasized affix */ /* Emphasized affix */
&.fuse-mat-emphasized-affix { &.fuse-mat-emphasized-affix {
.mat-mdc-text-field-wrapper { .mat-mdc-text-field-wrapper {
.mat-mdc-form-field-flex { .mat-mdc-form-field-flex {
.mat-mdc-form-field-icon-prefix, .mat-mdc-form-field-icon-prefix,
.mat-mdc-form-field-text-prefix { .mat-mdc-form-field-text-prefix {
align-self: stretch !important; align-self: stretch !important;
@ -798,9 +845,7 @@
margin-right: 8px; margin-right: 8px;
} }
> *:not(.mat-icon):not(.mat-mdc-icon-button):not( > *:not(.mat-icon):not(.mat-mdc-icon-button):not(.mat-mdc-select):not(.mat-datepicker-toggle) {
.mat-mdc-select
):not(.mat-datepicker-toggle) {
margin-right: 16px; margin-right: 16px;
} }
} }
@ -830,9 +875,7 @@
margin-left: 8px; margin-left: 8px;
} }
> *:not(.mat-icon):not(.mat-mdc-icon-button):not( > *:not(.mat-icon):not(.mat-mdc-icon-button):not(.mat-mdc-select):not(.mat-datepicker-toggle) {
.mat-mdc-select
):not(.mat-datepicker-toggle) {
margin-left: 16px; margin-left: 16px;
} }
} }
@ -848,8 +891,11 @@
/* with Textarea */ /* with Textarea */
&:has(textarea.mat-mdc-input-element) { &:has(textarea.mat-mdc-input-element) {
.mat-mdc-text-field-wrapper { .mat-mdc-text-field-wrapper {
.mat-mdc-form-field-flex { .mat-mdc-form-field-flex {
.mat-mdc-form-field-icon-prefix, .mat-mdc-form-field-icon-prefix,
.mat-mdc-form-field-text-prefix, .mat-mdc-form-field-text-prefix,
.mat-mdc-form-field-icon-suffix, .mat-mdc-form-field-icon-suffix,
@ -863,6 +909,7 @@
/* Bolder border width */ /* Bolder border width */
&.fuse-mat-bold { &.fuse-mat-bold {
.mat-mdc-text-field-wrapper { .mat-mdc-text-field-wrapper {
border-width: 2px !important; border-width: 2px !important;
} }
@ -871,8 +918,10 @@
/* "outline" appearance */ /* "outline" appearance */
.mat-mdc-form-field.mat-form-field-appearance-outline { .mat-mdc-form-field.mat-form-field-appearance-outline {
/* Invalid */ /* Invalid */
&.mat-form-field-invalid { &.mat-form-field-invalid {
.mdc-notched-outline__leading, .mdc-notched-outline__leading,
.mdc-notched-outline__notch, .mdc-notched-outline__notch,
.mdc-notched-outline__trailing { .mdc-notched-outline__trailing {
@ -882,8 +931,10 @@
/* Focused */ /* Focused */
&.mat-focused:not(.mat-form-field-invalid) { &.mat-focused:not(.mat-form-field-invalid) {
/* Primary */ /* Primary */
&.mat-primary { &.mat-primary {
.mdc-notched-outline__leading, .mdc-notched-outline__leading,
.mdc-notched-outline__notch, .mdc-notched-outline__notch,
.mdc-notched-outline__trailing { .mdc-notched-outline__trailing {
@ -893,6 +944,7 @@
/* Accent */ /* Accent */
&.mat-accent { &.mat-accent {
.mdc-notched-outline__leading, .mdc-notched-outline__leading,
.mdc-notched-outline__notch, .mdc-notched-outline__notch,
.mdc-notched-outline__trailing { .mdc-notched-outline__trailing {
@ -902,9 +954,13 @@
} }
&:not(.mat-focused):not(.mat-form-field-invalid) { &:not(.mat-focused):not(.mat-form-field-invalid) {
.mat-mdc-text-field-wrapper { .mat-mdc-text-field-wrapper {
.mat-mdc-form-field-flex { .mat-mdc-form-field-flex {
.mdc-notched-outline { .mdc-notched-outline {
.mdc-notched-outline__leading, .mdc-notched-outline__leading,
.mdc-notched-outline__notch, .mdc-notched-outline__notch,
.mdc-notched-outline__trailing { .mdc-notched-outline__trailing {
@ -918,8 +974,11 @@
/* Remove the extra border on the right side of the notch */ /* Remove the extra border on the right side of the notch */
/* Tailwind's global border setter causes this issue */ /* Tailwind's global border setter causes this issue */
.mat-mdc-text-field-wrapper { .mat-mdc-text-field-wrapper {
.mat-mdc-form-field-flex { .mat-mdc-form-field-flex {
.mdc-notched-outline { .mdc-notched-outline {
.mdc-notched-outline__notch { .mdc-notched-outline__notch {
border-right-style: none !important; border-right-style: none !important;
} }
@ -932,6 +991,7 @@
/* @ Datepicker /* @ Datepicker
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* @ Icon /* @ Icon
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
@ -952,6 +1012,7 @@
/* @ Inputs /* @ Inputs
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
.mat-mdc-input-element { .mat-mdc-input-element {
&::placeholder { &::placeholder {
transition: none !important; transition: none !important;
@apply text-hint #{'!important'}; @apply text-hint #{'!important'};
@ -975,7 +1036,9 @@
/* Invalid */ /* Invalid */
.mat-form-field-invalid { .mat-form-field-invalid {
.mat-mdc-input-element { .mat-mdc-input-element {
/* Placeholder color */ /* Placeholder color */
&::placeholder { &::placeholder {
@apply text-warn #{'!important'}; @apply text-warn #{'!important'};
@ -1002,11 +1065,22 @@
min-width: 144px !important; min-width: 144px !important;
.mat-mdc-menu-content { .mat-mdc-menu-content {
.mat-mdc-menu-item { .mat-mdc-menu-item {
.mat-mdc-menu-item-text { display: flex;
display: flex; align-items: center;
> span {
display: inline-flex;
align-items: center; align-items: center;
padding-right: 16px; }
&.mat-mdc-menu-item-submenu-trigger {
padding-right: 40px;
}
.mat-icon {
margin-right: 12px;
} }
.mat-icon-no-color { .mat-icon-no-color {
@ -1026,6 +1100,7 @@
/* @ Paginator /* @ Paginator
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
.mat-mdc-paginator { .mat-mdc-paginator {
.mat-mdc-paginator-container { .mat-mdc-paginator-container {
padding: 8px 16px; padding: 8px 16px;
justify-content: space-between; justify-content: space-between;
@ -1085,6 +1160,7 @@
} }
.mat-mdc-select-trigger { .mat-mdc-select-trigger {
.mat-mdc-select-value { .mat-mdc-select-value {
position: relative; position: relative;
display: flex; display: flex;
@ -1107,7 +1183,6 @@
.mat-mdc-select-arrow { .mat-mdc-select-arrow {
margin: 0 0 0 8px; margin: 0 0 0 8px;
@apply text-secondary #{!important};
} }
} }
} }
@ -1116,12 +1191,14 @@
/* @ Slide Toggle /* @ Slide Toggle
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* @ Snack bar /* @ Snack bar
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
.mat-mdc-snack-bar-container { .mat-mdc-snack-bar-container {
.mat-mdc-button.mat-mdc-snack-bar-action:not(:disabled) { .mat-mdc-button.mat-mdc-snack-bar-action:not(:disabled) {
color: #ffffff !important; color: #FFFFFF !important;
.dark & { .dark & {
color: #000000 !important; color: #000000 !important;
@ -1129,10 +1206,12 @@
} }
} }
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* @ Stepper /* @ Stepper
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
.mat-step-icon { .mat-step-icon {
/* Do not override the mat-icon color */ /* Do not override the mat-icon color */
.mat-icon { .mat-icon {
color: currentColor !important; color: currentColor !important;
@ -1148,6 +1227,7 @@
/* @ Table /* @ Table
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
.mat-mdc-table { .mat-mdc-table {
.mdc-data-table__row:not(.mdc-data-table__row--selected):hover { .mdc-data-table__row:not(.mdc-data-table__row--selected):hover {
background: none !important; background: none !important;
} }
@ -1157,8 +1237,10 @@
/* @ Tabs /* @ Tabs
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
.mat-mdc-tab-group { .mat-mdc-tab-group {
/* No header */ /* No header */
&.fuse-mat-no-header { &.fuse-mat-no-header {
.mat-mdc-tab-header { .mat-mdc-tab-header {
height: 0 !important; height: 0 !important;
max-height: 0 !important; max-height: 0 !important;
@ -1169,7 +1251,9 @@
} }
&:not(.mat-background-primary):not(.mat-background-accent) { &:not(.mat-background-primary):not(.mat-background-accent) {
.mat-mdc-tab-header { .mat-mdc-tab-header {
.mat-mdc-tab-label-container { .mat-mdc-tab-label-container {
box-shadow: inset 0 -1px var(--fuse-border); box-shadow: inset 0 -1px var(--fuse-border);
} }
@ -1177,6 +1261,7 @@
} }
.mat-mdc-tab-header { .mat-mdc-tab-header {
.mat-mdc-tab-label-container { .mat-mdc-tab-label-container {
margin: 0 24px; margin: 0 24px;
} }
@ -1198,8 +1283,10 @@ textarea.mat-mdc-input-element {
/* @ Toolbar /* @ Toolbar
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
.mat-toolbar { .mat-toolbar {
/* Apply primary contrast color */ /* Apply primary contrast color */
&.mat-primary { &.mat-primary {
.mat-icon { .mat-icon {
@apply text-on-primary #{'!important'}; @apply text-on-primary #{'!important'};
} }
@ -1223,6 +1310,7 @@ textarea.mat-mdc-input-element {
/* Apply accent contrast color */ /* Apply accent contrast color */
&.mat-accent { &.mat-accent {
.mat-icon { .mat-icon {
@apply text-on-accent #{'!important'}; @apply text-on-accent #{'!important'};
} }
@ -1246,6 +1334,7 @@ textarea.mat-mdc-input-element {
/* Apply warn contrast color */ /* Apply warn contrast color */
&.mat-warn { &.mat-warn {
.mat-icon { .mat-icon {
@apply text-on-warn #{'!important'}; @apply text-on-warn #{'!important'};
} }

View File

@ -3,35 +3,36 @@
/* ----------------------------------------------------------------------------------------------------- */ /* ----------------------------------------------------------------------------------------------------- */
code[class*='language-'], code[class*='language-'],
pre[class*='language-'] { pre[class*='language-'] {
.hljs-comment, .hljs-comment,
.hljs-quote { .hljs-quote {
color: #8b9fc1; color: #8B9FC1;
font-style: italic; font-style: italic;
} }
.hljs-doctag, .hljs-doctag,
.hljs-keyword, .hljs-keyword,
.hljs-formula { .hljs-formula {
color: #22d3ee; color: #22D3EE;
} }
.hljs-name { .hljs-name {
color: #e879f9; color: #E879F9;
} }
.hljs-tag { .hljs-tag {
color: #bae6fd; color: #BAE6FD;
} }
.hljs-section, .hljs-section,
.hljs-selector-tag, .hljs-selector-tag,
.hljs-deletion, .hljs-deletion,
.hljs-subst { .hljs-subst {
color: #f87f71; color: #F87F71;
} }
.hljs-literal { .hljs-literal {
color: #36beff; color: #36BEFF;
} }
.hljs-string, .hljs-string,
@ -39,12 +40,12 @@ pre[class*='language-'] {
.hljs-addition, .hljs-addition,
.hljs-attribute, .hljs-attribute,
.hljs-meta-string { .hljs-meta-string {
color: #bef264; color: #BEF264;
} }
.hljs-built_in, .hljs-built_in,
.hljs-class .hljs-title { .hljs-class .hljs-title {
color: #ffd374; color: #FFD374;
} }
.hljs-attr, .hljs-attr,
@ -55,7 +56,7 @@ pre[class*='language-'] {
.hljs-selector-attr, .hljs-selector-attr,
.hljs-selector-pseudo, .hljs-selector-pseudo,
.hljs-number { .hljs-number {
color: #22d3ee; color: #22D3EE;
} }
.hljs-symbol, .hljs-symbol,
@ -64,7 +65,7 @@ pre[class*='language-'] {
.hljs-meta, .hljs-meta,
.hljs-selector-id, .hljs-selector-id,
.hljs-title { .hljs-title {
color: #e879f9; color: #E879F9;
} }
.hljs-emphasis { .hljs-emphasis {

View File

@ -8,6 +8,7 @@
&.ps--focus, &.ps--focus,
&.ps--scrolling-x, &.ps--scrolling-x,
&.ps--scrolling-y { &.ps--scrolling-y {
> .ps__rail-x, > .ps__rail-x,
> .ps__rail-y { > .ps__rail-y {
opacity: 1; opacity: 1;

View File

@ -17,7 +17,9 @@
} }
.ql-picker { .ql-picker {
&.ql-expanded { &.ql-expanded {
.ql-picker-label { .ql-picker-label {
@apply border-gray-300; @apply border-gray-300;
@ -28,7 +30,7 @@
.ql-picker-options { .ql-picker-options {
z-index: 10 !important; z-index: 10 !important;
@apply bg-card border-gray-300; @apply border-gray-300 bg-card;
.dark & { .dark & {
@apply border-gray-500; @apply border-gray-500;
@ -41,6 +43,7 @@
} }
.ql-picker-options { .ql-picker-options {
.ql-picker-item { .ql-picker-item {
@apply text-default; @apply text-default;
} }
@ -100,10 +103,10 @@
} }
.ql-tooltip { .ql-tooltip {
@apply rounded-md border-gray-300 bg-gray-100 px-3 py-1 shadow-sm; @apply px-3 py-1 shadow-sm rounded-md bg-gray-100 border-gray-300;
.dark & { .dark & {
@apply border-gray-700 bg-gray-700 shadow-lg #{'!important'}; @apply shadow-lg bg-gray-700 border-gray-700 #{'!important'};
} }
// Label // Label
@ -113,10 +116,10 @@
.ql-action, .ql-action,
.ql-remove { .ql-remove {
@apply border-gray-300 text-primary; @apply text-primary border-gray-300;
.dark & { .dark & {
@apply border-gray-300 text-primary-400; @apply text-primary-400 border-gray-300;
} }
} }
@ -129,10 +132,10 @@
} }
input { input {
@apply text-default rounded-sm border-gray-300 bg-white #{'!important'}; @apply rounded-sm text-default bg-white border-gray-300 #{'!important'};
.dark & { .dark & {
@apply border-gray-500 bg-gray-700 #{'!important'}; @apply bg-gray-700 border-gray-500 #{'!important'};
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More